diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts index aa7358335..8ddb56dec 100644 --- a/apps/api/src/index.ts +++ b/apps/api/src/index.ts @@ -5,7 +5,11 @@ import { zValidator } from "@hono/zod-validator"; import { Inngest } from "inngest"; import { serve as serveInngest } from "inngest/hono"; import { logger } from "./logger.js"; -import { type DeployJob, deployJobSchema } from "./schema.js"; +import { + cancelDeploymentSchema, + type DeployJob, + deployJobSchema, +} from "./schema.js"; import { deploy } from "./utils.js"; const app = new Hono(); @@ -27,6 +31,13 @@ export const deploymentFunction = inngest.createFunction( }, ], retries: 0, + cancelOn: [ + { + event: "deployment/cancelled", + if: "async.data.applicationId == event.data.applicationId || async.data.composeId == event.data.composeId", + timeout: "1h", // Allow cancellation for up to 1 hour + }, + ], }, { event: "deployment/requested" }, @@ -119,6 +130,48 @@ app.post("/deploy", zValidator("json", deployJobSchema), async (c) => { } }); +app.post( + "/cancel-deployment", + zValidator("json", cancelDeploymentSchema), + async (c) => { + const data = c.req.valid("json"); + logger.info("Received cancel deployment request", data); + + try { + // Send cancellation event to Inngest + + await inngest.send({ + name: "deployment/cancelled", + data, + }); + + const identifier = + data.applicationType === "application" + ? `applicationId: ${data.applicationId}` + : `composeId: ${data.composeId}`; + + logger.info("Deployment cancellation event sent", { + ...data, + identifier, + }); + + return c.json({ + message: "Deployment cancellation requested", + applicationType: data.applicationType, + }); + } catch (error) { + logger.error("Failed to send deployment cancellation event", error); + return c.json( + { + message: "Failed to cancel deployment", + error: error instanceof Error ? error.message : String(error), + }, + 500, + ); + } + }, +); + app.get("/health", async (c) => { return c.json({ status: "ok" }); }); diff --git a/apps/api/src/schema.ts b/apps/api/src/schema.ts index 609289bf7..5a4355956 100644 --- a/apps/api/src/schema.ts +++ b/apps/api/src/schema.ts @@ -3,8 +3,8 @@ import { z } from "zod"; export const deployJobSchema = z.discriminatedUnion("applicationType", [ z.object({ applicationId: z.string(), - titleLog: z.string(), - descriptionLog: z.string(), + titleLog: z.string().optional(), + descriptionLog: z.string().optional(), server: z.boolean().optional(), type: z.enum(["deploy", "redeploy"]), applicationType: z.literal("application"), @@ -12,8 +12,8 @@ export const deployJobSchema = z.discriminatedUnion("applicationType", [ }), z.object({ composeId: z.string(), - titleLog: z.string(), - descriptionLog: z.string(), + titleLog: z.string().optional(), + descriptionLog: z.string().optional(), server: z.boolean().optional(), type: z.enum(["deploy", "redeploy"]), applicationType: z.literal("compose"), @@ -22,8 +22,8 @@ export const deployJobSchema = z.discriminatedUnion("applicationType", [ z.object({ applicationId: z.string(), previewDeploymentId: z.string(), - titleLog: z.string(), - descriptionLog: z.string(), + titleLog: z.string().optional(), + descriptionLog: z.string().optional(), server: z.boolean().optional(), type: z.enum(["deploy"]), applicationType: z.literal("application-preview"), @@ -32,3 +32,16 @@ export const deployJobSchema = z.discriminatedUnion("applicationType", [ ]); export type DeployJob = z.infer; + +export const cancelDeploymentSchema = z.discriminatedUnion("applicationType", [ + z.object({ + applicationId: z.string(), + applicationType: z.literal("application"), + }), + z.object({ + composeId: z.string(), + applicationType: z.literal("compose"), + }), +]); + +export type CancelDeploymentJob = z.infer; diff --git a/apps/api/src/utils.ts b/apps/api/src/utils.ts index ee3943d34..ee2ac3e50 100644 --- a/apps/api/src/utils.ts +++ b/apps/api/src/utils.ts @@ -18,14 +18,14 @@ export const deploy = async (job: DeployJob) => { if (job.type === "redeploy") { await rebuildRemoteApplication({ applicationId: job.applicationId, - titleLog: job.titleLog, - descriptionLog: job.descriptionLog, + titleLog: job.titleLog || "Rebuild deployment", + descriptionLog: job.descriptionLog || "", }); } else if (job.type === "deploy") { await deployRemoteApplication({ applicationId: job.applicationId, - titleLog: job.titleLog, - descriptionLog: job.descriptionLog, + titleLog: job.titleLog || "Manual deployment", + descriptionLog: job.descriptionLog || "", }); } } @@ -38,14 +38,14 @@ export const deploy = async (job: DeployJob) => { if (job.type === "redeploy") { await rebuildRemoteCompose({ composeId: job.composeId, - titleLog: job.titleLog, - descriptionLog: job.descriptionLog, + titleLog: job.titleLog || "Rebuild deployment", + descriptionLog: job.descriptionLog || "", }); } else if (job.type === "deploy") { await deployRemoteCompose({ composeId: job.composeId, - titleLog: job.titleLog, - descriptionLog: job.descriptionLog, + titleLog: job.titleLog || "Manual deployment", + descriptionLog: job.descriptionLog || "", }); } } @@ -57,8 +57,8 @@ export const deploy = async (job: DeployJob) => { if (job.type === "deploy") { await deployRemotePreviewApplication({ applicationId: job.applicationId, - titleLog: job.titleLog, - descriptionLog: job.descriptionLog, + titleLog: job.titleLog || "Preview Deployment", + descriptionLog: job.descriptionLog || "", previewDeploymentId: job.previewDeploymentId, }); } diff --git a/apps/dokploy/__test__/compose/compose.test.ts b/apps/dokploy/__test__/compose/compose.test.ts index 69d3a5212..b691537a1 100644 --- a/apps/dokploy/__test__/compose/compose.test.ts +++ b/apps/dokploy/__test__/compose/compose.test.ts @@ -1,7 +1,7 @@ import type { ComposeSpecification } from "@dokploy/server"; import { addSuffixToAllProperties } from "@dokploy/server"; -import { load } from "js-yaml"; import { expect, test } from "vitest"; +import { parse } from "yaml"; const composeFile1 = ` version: "3.8" @@ -61,7 +61,7 @@ secrets: file: ./db_password.txt `; -const expectedComposeFile1 = load(` +const expectedComposeFile1 = parse(` version: "3.8" services: @@ -120,7 +120,7 @@ secrets: `) as ComposeSpecification; test("Add suffix to all properties in compose file 1", () => { - const composeData = load(composeFile1) as ComposeSpecification; + const composeData = parse(composeFile1) as ComposeSpecification; const suffix = "testhash"; const updatedComposeData = addSuffixToAllProperties(composeData, suffix); @@ -185,7 +185,7 @@ secrets: file: ./db_password.txt `; -const expectedComposeFile2 = load(` +const expectedComposeFile2 = parse(` version: "3.8" services: @@ -243,7 +243,7 @@ secrets: `) as ComposeSpecification; test("Add suffix to all properties in compose file 2", () => { - const composeData = load(composeFile2) as ComposeSpecification; + const composeData = parse(composeFile2) as ComposeSpecification; const suffix = "testhash"; const updatedComposeData = addSuffixToAllProperties(composeData, suffix); @@ -308,7 +308,7 @@ secrets: file: ./service_secret.txt `; -const expectedComposeFile3 = load(` +const expectedComposeFile3 = parse(` version: "3.8" services: @@ -366,7 +366,7 @@ secrets: `) as ComposeSpecification; test("Add suffix to all properties in compose file 3", () => { - const composeData = load(composeFile3) as ComposeSpecification; + const composeData = parse(composeFile3) as ComposeSpecification; const suffix = "testhash"; const updatedComposeData = addSuffixToAllProperties(composeData, suffix); @@ -420,7 +420,7 @@ volumes: driver: local `; -const expectedComposeFile = load(` +const expectedComposeFile = parse(` version: "3.8" services: @@ -467,7 +467,7 @@ volumes: `) as ComposeSpecification; test("Add suffix to all properties in Plausible compose file", () => { - const composeData = load(composeFile) as ComposeSpecification; + const composeData = parse(composeFile) as ComposeSpecification; const suffix = "testhash"; const updatedComposeData = addSuffixToAllProperties(composeData, suffix); diff --git a/apps/dokploy/__test__/compose/config/config-root.test.ts b/apps/dokploy/__test__/compose/config/config-root.test.ts index 668e17902..a633bab53 100644 --- a/apps/dokploy/__test__/compose/config/config-root.test.ts +++ b/apps/dokploy/__test__/compose/config/config-root.test.ts @@ -1,7 +1,7 @@ import type { ComposeSpecification } from "@dokploy/server"; import { addSuffixToConfigsRoot, generateRandomHash } from "@dokploy/server"; -import { load } from "js-yaml"; import { expect, test } from "vitest"; +import { parse } from "yaml"; test("Generate random hash with 8 characters", () => { const hash = generateRandomHash(); @@ -23,7 +23,7 @@ configs: `; test("Add suffix to configs in root property", () => { - const composeData = load(composeFile) as ComposeSpecification; + const composeData = parse(composeFile) as ComposeSpecification; const suffix = generateRandomHash(); @@ -59,7 +59,7 @@ configs: `; test("Add suffix to multiple configs in root property", () => { - const composeData = load(composeFileMultipleConfigs) as ComposeSpecification; + const composeData = parse(composeFileMultipleConfigs) as ComposeSpecification; const suffix = generateRandomHash(); @@ -92,7 +92,7 @@ configs: `; test("Add suffix to configs with different properties in root property", () => { - const composeData = load( + const composeData = parse( composeFileDifferentProperties, ) as ComposeSpecification; @@ -137,7 +137,7 @@ configs: `; // Expected compose file con el prefijo `testhash` -const expectedComposeFileConfigRoot = load(` +const expectedComposeFileConfigRoot = parse(` version: "3.8" services: @@ -162,7 +162,7 @@ configs: `) as ComposeSpecification; test("Add suffix to configs in root property", () => { - const composeData = load(composeFileConfigRoot) as ComposeSpecification; + const composeData = parse(composeFileConfigRoot) as ComposeSpecification; const suffix = "testhash"; diff --git a/apps/dokploy/__test__/compose/config/config-service.test.ts b/apps/dokploy/__test__/compose/config/config-service.test.ts index 246872f09..08dd696e6 100644 --- a/apps/dokploy/__test__/compose/config/config-service.test.ts +++ b/apps/dokploy/__test__/compose/config/config-service.test.ts @@ -3,8 +3,8 @@ import { addSuffixToConfigsInServices, generateRandomHash, } from "@dokploy/server"; -import { load } from "js-yaml"; import { expect, test } from "vitest"; +import { parse } from "yaml"; const composeFile = ` version: "3.8" @@ -22,7 +22,7 @@ configs: `; test("Add suffix to configs in services", () => { - const composeData = load(composeFile) as ComposeSpecification; + const composeData = parse(composeFile) as ComposeSpecification; const suffix = generateRandomHash(); @@ -54,7 +54,7 @@ configs: `; test("Add suffix to configs in services with single config", () => { - const composeData = load( + const composeData = parse( composeFileSingleServiceConfig, ) as ComposeSpecification; @@ -108,7 +108,7 @@ configs: `; test("Add suffix to configs in services with multiple configs", () => { - const composeData = load( + const composeData = parse( composeFileMultipleServicesConfigs, ) as ComposeSpecification; @@ -157,7 +157,7 @@ services: `; // Expected compose file con el prefijo `testhash` -const expectedComposeFileConfigServices = load(` +const expectedComposeFileConfigServices = parse(` version: "3.8" services: @@ -182,7 +182,7 @@ services: `) as ComposeSpecification; test("Add suffix to configs in services", () => { - const composeData = load(composeFileConfigServices) as ComposeSpecification; + const composeData = parse(composeFileConfigServices) as ComposeSpecification; const suffix = "testhash"; diff --git a/apps/dokploy/__test__/compose/config/config.test.ts b/apps/dokploy/__test__/compose/config/config.test.ts index 2d5feeb9a..3a160431e 100644 --- a/apps/dokploy/__test__/compose/config/config.test.ts +++ b/apps/dokploy/__test__/compose/config/config.test.ts @@ -1,7 +1,7 @@ import type { ComposeSpecification } from "@dokploy/server"; import { addSuffixToAllConfigs, generateRandomHash } from "@dokploy/server"; -import { load } from "js-yaml"; import { expect, test } from "vitest"; +import { parse } from "yaml"; test("Generate random hash with 8 characters", () => { const hash = generateRandomHash(); @@ -43,7 +43,7 @@ configs: file: ./db-config.yml `; -const expectedComposeFileCombinedConfigs = load(` +const expectedComposeFileCombinedConfigs = parse(` version: "3.8" services: @@ -77,7 +77,7 @@ configs: `) as ComposeSpecification; test("Add suffix to all configs in root and services", () => { - const composeData = load(composeFileCombinedConfigs) as ComposeSpecification; + const composeData = parse(composeFileCombinedConfigs) as ComposeSpecification; const suffix = "testhash"; @@ -122,7 +122,7 @@ configs: file: ./db-config.yml `; -const expectedComposeFileWithEnvAndExternal = load(` +const expectedComposeFileWithEnvAndExternal = parse(` version: "3.8" services: @@ -159,7 +159,7 @@ configs: `) as ComposeSpecification; test("Add suffix to configs with environment and external", () => { - const composeData = load( + const composeData = parse( composeFileWithEnvAndExternal, ) as ComposeSpecification; @@ -200,7 +200,7 @@ configs: file: ./db-config.yml `; -const expectedComposeFileWithTemplateDriverAndLabels = load(` +const expectedComposeFileWithTemplateDriverAndLabels = parse(` version: "3.8" services: @@ -231,7 +231,7 @@ configs: `) as ComposeSpecification; test("Add suffix to configs with template driver and labels", () => { - const composeData = load( + const composeData = parse( composeFileWithTemplateDriverAndLabels, ) as ComposeSpecification; diff --git a/apps/dokploy/__test__/compose/network/network-root.test.ts b/apps/dokploy/__test__/compose/network/network-root.test.ts index c55f6fa86..0d3c841d4 100644 --- a/apps/dokploy/__test__/compose/network/network-root.test.ts +++ b/apps/dokploy/__test__/compose/network/network-root.test.ts @@ -1,7 +1,7 @@ import type { ComposeSpecification } from "@dokploy/server"; import { addSuffixToNetworksRoot, generateRandomHash } from "@dokploy/server"; -import { load } from "js-yaml"; import { expect, test } from "vitest"; +import { parse } from "yaml"; const composeFile = ` version: "3.8" @@ -35,7 +35,7 @@ test("Generate random hash with 8 characters", () => { }); test("Add suffix to networks root property", () => { - const composeData = load(composeFile) as ComposeSpecification; + const composeData = parse(composeFile) as ComposeSpecification; const suffix = generateRandomHash(); @@ -79,7 +79,7 @@ networks: `; test("Add suffix to advanced networks root property (2 TRY)", () => { - const composeData = load(composeFile2) as ComposeSpecification; + const composeData = parse(composeFile2) as ComposeSpecification; const suffix = generateRandomHash(); @@ -120,7 +120,7 @@ networks: `; test("Add suffix to networks with external properties", () => { - const composeData = load(composeFile3) as ComposeSpecification; + const composeData = parse(composeFile3) as ComposeSpecification; const suffix = generateRandomHash(); @@ -160,7 +160,7 @@ networks: `; test("Add suffix to networks with IPAM configurations", () => { - const composeData = load(composeFile4) as ComposeSpecification; + const composeData = parse(composeFile4) as ComposeSpecification; const suffix = generateRandomHash(); @@ -201,7 +201,7 @@ networks: `; test("Add suffix to networks with custom options", () => { - const composeData = load(composeFile5) as ComposeSpecification; + const composeData = parse(composeFile5) as ComposeSpecification; const suffix = generateRandomHash(); @@ -264,7 +264,7 @@ networks: `; test("Add suffix to networks with static suffix", () => { - const composeData = load(composeFile6) as ComposeSpecification; + const composeData = parse(composeFile6) as ComposeSpecification; const suffix = "testhash"; @@ -273,7 +273,7 @@ test("Add suffix to networks with static suffix", () => { } const networks = addSuffixToNetworksRoot(composeData.networks, suffix); - const expectedComposeData = load( + const expectedComposeData = parse( expectedComposeFile6, ) as ComposeSpecification; expect(networks).toStrictEqual(expectedComposeData.networks); @@ -293,7 +293,7 @@ networks: `; test("It shoudn't add suffix to dokploy-network", () => { - const composeData = load(composeFile7) as ComposeSpecification; + const composeData = parse(composeFile7) as ComposeSpecification; const suffix = generateRandomHash(); diff --git a/apps/dokploy/__test__/compose/network/network-service.test.ts b/apps/dokploy/__test__/compose/network/network-service.test.ts index 3cf46d4ab..e07fa1546 100644 --- a/apps/dokploy/__test__/compose/network/network-service.test.ts +++ b/apps/dokploy/__test__/compose/network/network-service.test.ts @@ -3,8 +3,8 @@ import { addSuffixToServiceNetworks, generateRandomHash, } from "@dokploy/server"; -import { load } from "js-yaml"; import { expect, test } from "vitest"; +import { parse } from "yaml"; const composeFile = ` version: "3.8" @@ -23,7 +23,7 @@ services: `; test("Add suffix to networks in services", () => { - const composeData = load(composeFile) as ComposeSpecification; + const composeData = parse(composeFile) as ComposeSpecification; const suffix = generateRandomHash(); @@ -67,7 +67,7 @@ networks: `; test("Add suffix to networks in services with aliases", () => { - const composeData = load(composeFile2) as ComposeSpecification; + const composeData = parse(composeFile2) as ComposeSpecification; const suffix = generateRandomHash(); @@ -107,7 +107,7 @@ networks: `; test("Add suffix to networks in services (Object with simple networks)", () => { - const composeData = load(composeFile3) as ComposeSpecification; + const composeData = parse(composeFile3) as ComposeSpecification; const suffix = generateRandomHash(); @@ -153,7 +153,7 @@ networks: `; test("Add suffix to networks in services (combined case)", () => { - const composeData = load(composeFileCombined) as ComposeSpecification; + const composeData = parse(composeFileCombined) as ComposeSpecification; const suffix = generateRandomHash(); @@ -196,7 +196,7 @@ services: `; test("It shoudn't add suffix to dokploy-network in services", () => { - const composeData = load(composeFile7) as ComposeSpecification; + const composeData = parse(composeFile7) as ComposeSpecification; const suffix = generateRandomHash(); @@ -245,7 +245,7 @@ services: `; test("It shoudn't add suffix to dokploy-network in services multiples cases", () => { - const composeData = load(composeFile8) as ComposeSpecification; + const composeData = parse(composeFile8) as ComposeSpecification; const suffix = generateRandomHash(); diff --git a/apps/dokploy/__test__/compose/network/network.test.ts b/apps/dokploy/__test__/compose/network/network.test.ts index 7ba1c6a83..c1900ed74 100644 --- a/apps/dokploy/__test__/compose/network/network.test.ts +++ b/apps/dokploy/__test__/compose/network/network.test.ts @@ -5,8 +5,8 @@ import { addSuffixToServiceNetworks, generateRandomHash, } from "@dokploy/server"; -import { load } from "js-yaml"; import { expect, test } from "vitest"; +import { parse } from "yaml"; const composeFileCombined = ` version: "3.8" @@ -39,7 +39,7 @@ networks: `; test("Add suffix to networks in services and root (combined case)", () => { - const composeData = load(composeFileCombined) as ComposeSpecification; + const composeData = parse(composeFileCombined) as ComposeSpecification; const suffix = generateRandomHash(); @@ -89,7 +89,7 @@ test("Add suffix to networks in services and root (combined case)", () => { expect(redisNetworks).not.toHaveProperty("backend"); }); -const expectedComposeFile = load(` +const expectedComposeFile = parse(` version: "3.8" services: @@ -120,7 +120,7 @@ networks: `); test("Add suffix to networks in compose file", () => { - const composeData = load(composeFileCombined) as ComposeSpecification; + const composeData = parse(composeFileCombined) as ComposeSpecification; const suffix = "testhash"; if (!composeData?.networks) { @@ -156,7 +156,7 @@ networks: driver: bridge `; -const expectedComposeFile2 = load(` +const expectedComposeFile2 = parse(` version: "3.8" services: @@ -182,7 +182,7 @@ networks: `); test("Add suffix to networks in compose file with external and internal networks", () => { - const composeData = load(composeFile2) as ComposeSpecification; + const composeData = parse(composeFile2) as ComposeSpecification; const suffix = "testhash"; const updatedComposeData = addSuffixToAllNetworks(composeData, suffix); @@ -218,7 +218,7 @@ networks: com.docker.network.bridge.enable_icc: "true" `; -const expectedComposeFile3 = load(` +const expectedComposeFile3 = parse(` version: "3.8" services: @@ -247,7 +247,7 @@ networks: `); test("Add suffix to networks in compose file with multiple services and complex network configurations", () => { - const composeData = load(composeFile3) as ComposeSpecification; + const composeData = parse(composeFile3) as ComposeSpecification; const suffix = "testhash"; const updatedComposeData = addSuffixToAllNetworks(composeData, suffix); @@ -289,7 +289,7 @@ networks: `; -const expectedComposeFile4 = load(` +const expectedComposeFile4 = parse(` version: "3.8" services: @@ -326,7 +326,7 @@ networks: `); test("Expect don't add suffix to dokploy-network in compose file with multiple services and complex network configurations", () => { - const composeData = load(composeFile4) as ComposeSpecification; + const composeData = parse(composeFile4) as ComposeSpecification; const suffix = "testhash"; const updatedComposeData = addSuffixToAllNetworks(composeData, suffix); diff --git a/apps/dokploy/__test__/compose/secrets/secret-root.test.ts b/apps/dokploy/__test__/compose/secrets/secret-root.test.ts index b8cef56e4..ef74d64cf 100644 --- a/apps/dokploy/__test__/compose/secrets/secret-root.test.ts +++ b/apps/dokploy/__test__/compose/secrets/secret-root.test.ts @@ -1,7 +1,7 @@ import type { ComposeSpecification } from "@dokploy/server"; import { addSuffixToSecretsRoot, generateRandomHash } from "@dokploy/server"; -import { load } from "js-yaml"; import { expect, test } from "vitest"; +import { parse } from "yaml"; test("Generate random hash with 8 characters", () => { const hash = generateRandomHash(); @@ -23,7 +23,7 @@ secrets: `; test("Add suffix to secrets in root property", () => { - const composeData = load(composeFileSecretsRoot) as ComposeSpecification; + const composeData = parse(composeFileSecretsRoot) as ComposeSpecification; const suffix = generateRandomHash(); if (!composeData?.secrets) { @@ -52,7 +52,7 @@ secrets: `; test("Add suffix to secrets in root property (Test 1)", () => { - const composeData = load(composeFileSecretsRoot1) as ComposeSpecification; + const composeData = parse(composeFileSecretsRoot1) as ComposeSpecification; const suffix = generateRandomHash(); if (!composeData?.secrets) { @@ -84,7 +84,7 @@ secrets: `; test("Add suffix to secrets in root property (Test 2)", () => { - const composeData = load(composeFileSecretsRoot2) as ComposeSpecification; + const composeData = parse(composeFileSecretsRoot2) as ComposeSpecification; const suffix = generateRandomHash(); if (!composeData?.secrets) { diff --git a/apps/dokploy/__test__/compose/secrets/secret-services.test.ts b/apps/dokploy/__test__/compose/secrets/secret-services.test.ts index e12f611d0..a378bd606 100644 --- a/apps/dokploy/__test__/compose/secrets/secret-services.test.ts +++ b/apps/dokploy/__test__/compose/secrets/secret-services.test.ts @@ -3,8 +3,8 @@ import { addSuffixToSecretsInServices, generateRandomHash, } from "@dokploy/server"; -import { load } from "js-yaml"; import { expect, test } from "vitest"; +import { parse } from "yaml"; const composeFileSecretsServices = ` version: "3.8" @@ -21,7 +21,7 @@ secrets: `; test("Add suffix to secrets in services", () => { - const composeData = load(composeFileSecretsServices) as ComposeSpecification; + const composeData = parse(composeFileSecretsServices) as ComposeSpecification; const suffix = generateRandomHash(); if (!composeData.services) { @@ -54,7 +54,9 @@ secrets: `; test("Add suffix to secrets in services (Test 1)", () => { - const composeData = load(composeFileSecretsServices1) as ComposeSpecification; + const composeData = parse( + composeFileSecretsServices1, + ) as ComposeSpecification; const suffix = generateRandomHash(); if (!composeData.services) { @@ -93,7 +95,9 @@ secrets: `; test("Add suffix to secrets in services (Test 2)", () => { - const composeData = load(composeFileSecretsServices2) as ComposeSpecification; + const composeData = parse( + composeFileSecretsServices2, + ) as ComposeSpecification; const suffix = generateRandomHash(); if (!composeData.services) { diff --git a/apps/dokploy/__test__/compose/secrets/secret.test.ts b/apps/dokploy/__test__/compose/secrets/secret.test.ts index 3ff524ad7..3f6544bf1 100644 --- a/apps/dokploy/__test__/compose/secrets/secret.test.ts +++ b/apps/dokploy/__test__/compose/secrets/secret.test.ts @@ -1,7 +1,7 @@ import type { ComposeSpecification } from "@dokploy/server"; import { addSuffixToAllSecrets } from "@dokploy/server"; -import { load } from "js-yaml"; import { expect, test } from "vitest"; +import { parse } from "yaml"; const composeFileCombinedSecrets = ` version: "3.8" @@ -25,7 +25,7 @@ secrets: file: ./app_secret.txt `; -const expectedComposeFileCombinedSecrets = load(` +const expectedComposeFileCombinedSecrets = parse(` version: "3.8" services: @@ -48,7 +48,7 @@ secrets: `) as ComposeSpecification; test("Add suffix to all secrets", () => { - const composeData = load(composeFileCombinedSecrets) as ComposeSpecification; + const composeData = parse(composeFileCombinedSecrets) as ComposeSpecification; const suffix = "testhash"; const updatedComposeData = addSuffixToAllSecrets(composeData, suffix); @@ -77,7 +77,7 @@ secrets: file: ./cache_secret.txt `; -const expectedComposeFileCombinedSecrets3 = load(` +const expectedComposeFileCombinedSecrets3 = parse(` version: "3.8" services: @@ -99,7 +99,9 @@ secrets: `) as ComposeSpecification; test("Add suffix to all secrets (3rd Case)", () => { - const composeData = load(composeFileCombinedSecrets3) as ComposeSpecification; + const composeData = parse( + composeFileCombinedSecrets3, + ) as ComposeSpecification; const suffix = "testhash"; const updatedComposeData = addSuffixToAllSecrets(composeData, suffix); @@ -128,7 +130,7 @@ secrets: file: ./db_password.txt `; -const expectedComposeFileCombinedSecrets4 = load(` +const expectedComposeFileCombinedSecrets4 = parse(` version: "3.8" services: @@ -150,7 +152,9 @@ secrets: `) as ComposeSpecification; test("Add suffix to all secrets (4th Case)", () => { - const composeData = load(composeFileCombinedSecrets4) as ComposeSpecification; + const composeData = parse( + composeFileCombinedSecrets4, + ) as ComposeSpecification; const suffix = "testhash"; const updatedComposeData = addSuffixToAllSecrets(composeData, suffix); diff --git a/apps/dokploy/__test__/compose/service/service-container-name.test.ts b/apps/dokploy/__test__/compose/service/service-container-name.test.ts index 6ad45c588..d6521464d 100644 --- a/apps/dokploy/__test__/compose/service/service-container-name.test.ts +++ b/apps/dokploy/__test__/compose/service/service-container-name.test.ts @@ -1,7 +1,7 @@ import type { ComposeSpecification } from "@dokploy/server"; import { addSuffixToServiceNames, generateRandomHash } from "@dokploy/server"; -import { load } from "js-yaml"; import { expect, test } from "vitest"; +import { parse } from "yaml"; const composeFile = ` version: "3.8" @@ -27,7 +27,7 @@ test("Generate random hash with 8 characters", () => { }); test("Add suffix to service names with container_name in compose file", () => { - const composeData = load(composeFile) as ComposeSpecification; + const composeData = parse(composeFile) as ComposeSpecification; const suffix = generateRandomHash(); diff --git a/apps/dokploy/__test__/compose/service/service-depends-on.test.ts b/apps/dokploy/__test__/compose/service/service-depends-on.test.ts index 14a5789c4..547c309d5 100644 --- a/apps/dokploy/__test__/compose/service/service-depends-on.test.ts +++ b/apps/dokploy/__test__/compose/service/service-depends-on.test.ts @@ -1,7 +1,7 @@ import type { ComposeSpecification } from "@dokploy/server"; import { addSuffixToServiceNames, generateRandomHash } from "@dokploy/server"; -import { load } from "js-yaml"; import { expect, test } from "vitest"; +import { parse } from "yaml"; test("Generate random hash with 8 characters", () => { const hash = generateRandomHash(); @@ -32,7 +32,7 @@ networks: `; test("Add suffix to service names with depends_on (array) in compose file", () => { - const composeData = load(composeFile4) as ComposeSpecification; + const composeData = parse(composeFile4) as ComposeSpecification; const suffix = generateRandomHash(); @@ -102,7 +102,7 @@ networks: `; test("Add suffix to service names with depends_on (object) in compose file", () => { - const composeData = load(composeFile5) as ComposeSpecification; + const composeData = parse(composeFile5) as ComposeSpecification; const suffix = generateRandomHash(); diff --git a/apps/dokploy/__test__/compose/service/service-extends.test.ts b/apps/dokploy/__test__/compose/service/service-extends.test.ts index 0b7e92c53..f539eeebd 100644 --- a/apps/dokploy/__test__/compose/service/service-extends.test.ts +++ b/apps/dokploy/__test__/compose/service/service-extends.test.ts @@ -1,7 +1,7 @@ import type { ComposeSpecification } from "@dokploy/server"; import { addSuffixToServiceNames, generateRandomHash } from "@dokploy/server"; -import { load } from "js-yaml"; import { expect, test } from "vitest"; +import { parse } from "yaml"; test("Generate random hash with 8 characters", () => { const hash = generateRandomHash(); @@ -30,7 +30,7 @@ networks: `; test("Add suffix to service names with extends (string) in compose file", () => { - const composeData = load(composeFile6) as ComposeSpecification; + const composeData = parse(composeFile6) as ComposeSpecification; const suffix = generateRandomHash(); @@ -90,7 +90,7 @@ networks: `; test("Add suffix to service names with extends (object) in compose file", () => { - const composeData = load(composeFile7) as ComposeSpecification; + const composeData = parse(composeFile7) as ComposeSpecification; const suffix = generateRandomHash(); diff --git a/apps/dokploy/__test__/compose/service/service-links.test.ts b/apps/dokploy/__test__/compose/service/service-links.test.ts index 6c8cde39e..4187edce8 100644 --- a/apps/dokploy/__test__/compose/service/service-links.test.ts +++ b/apps/dokploy/__test__/compose/service/service-links.test.ts @@ -1,7 +1,7 @@ import type { ComposeSpecification } from "@dokploy/server"; import { addSuffixToServiceNames, generateRandomHash } from "@dokploy/server"; -import { load } from "js-yaml"; import { expect, test } from "vitest"; +import { parse } from "yaml"; test("Generate random hash with 8 characters", () => { const hash = generateRandomHash(); @@ -31,7 +31,7 @@ networks: `; test("Add suffix to service names with links in compose file", () => { - const composeData = load(composeFile2) as ComposeSpecification; + const composeData = parse(composeFile2) as ComposeSpecification; const suffix = generateRandomHash(); diff --git a/apps/dokploy/__test__/compose/service/service-names.test.ts b/apps/dokploy/__test__/compose/service/service-names.test.ts index c65299b03..c9c9d78c1 100644 --- a/apps/dokploy/__test__/compose/service/service-names.test.ts +++ b/apps/dokploy/__test__/compose/service/service-names.test.ts @@ -1,7 +1,7 @@ import type { ComposeSpecification } from "@dokploy/server"; import { addSuffixToServiceNames, generateRandomHash } from "@dokploy/server"; -import { load } from "js-yaml"; import { expect, test } from "vitest"; +import { parse } from "yaml"; test("Generate random hash with 8 characters", () => { const hash = generateRandomHash(); @@ -26,7 +26,7 @@ networks: `; test("Add suffix to service names in compose file", () => { - const composeData = load(composeFile) as ComposeSpecification; + const composeData = parse(composeFile) as ComposeSpecification; const suffix = generateRandomHash(); diff --git a/apps/dokploy/__test__/compose/service/service.test.ts b/apps/dokploy/__test__/compose/service/service.test.ts index 38895e073..a58e16722 100644 --- a/apps/dokploy/__test__/compose/service/service.test.ts +++ b/apps/dokploy/__test__/compose/service/service.test.ts @@ -3,8 +3,8 @@ import { addSuffixToAllServiceNames, addSuffixToServiceNames, } from "@dokploy/server"; -import { load } from "js-yaml"; import { expect, test } from "vitest"; +import { parse } from "yaml"; const composeFileCombinedAllCases = ` version: "3.8" @@ -38,7 +38,7 @@ networks: driver: bridge `; -const expectedComposeFile = load(` +const expectedComposeFile = parse(` version: "3.8" services: @@ -71,7 +71,9 @@ networks: `); test("Add suffix to all service names in compose file", () => { - const composeData = load(composeFileCombinedAllCases) as ComposeSpecification; + const composeData = parse( + composeFileCombinedAllCases, + ) as ComposeSpecification; const suffix = "testhash"; @@ -131,7 +133,7 @@ networks: driver: bridge `; -const expectedComposeFile1 = load(` +const expectedComposeFile1 = parse(` version: "3.8" services: @@ -176,7 +178,7 @@ networks: `) as ComposeSpecification; test("Add suffix to all service names in compose file 1", () => { - const composeData = load(composeFile1) as ComposeSpecification; + const composeData = parse(composeFile1) as ComposeSpecification; const suffix = "testhash"; const updatedComposeData = addSuffixToAllServiceNames(composeData, suffix); @@ -227,7 +229,7 @@ networks: driver: bridge `; -const expectedComposeFile2 = load(` +const expectedComposeFile2 = parse(` version: "3.8" services: @@ -271,7 +273,7 @@ networks: `) as ComposeSpecification; test("Add suffix to all service names in compose file 2", () => { - const composeData = load(composeFile2) as ComposeSpecification; + const composeData = parse(composeFile2) as ComposeSpecification; const suffix = "testhash"; const updatedComposeData = addSuffixToAllServiceNames(composeData, suffix); @@ -322,7 +324,7 @@ networks: driver: bridge `; -const expectedComposeFile3 = load(` +const expectedComposeFile3 = parse(` version: "3.8" services: @@ -366,7 +368,7 @@ networks: `) as ComposeSpecification; test("Add suffix to all service names in compose file 3", () => { - const composeData = load(composeFile3) as ComposeSpecification; + const composeData = parse(composeFile3) as ComposeSpecification; const suffix = "testhash"; const updatedComposeData = addSuffixToAllServiceNames(composeData, suffix); diff --git a/apps/dokploy/__test__/compose/service/sevice-volumes-from.test.ts b/apps/dokploy/__test__/compose/service/sevice-volumes-from.test.ts index 8aa8296e8..1de94b894 100644 --- a/apps/dokploy/__test__/compose/service/sevice-volumes-from.test.ts +++ b/apps/dokploy/__test__/compose/service/sevice-volumes-from.test.ts @@ -1,7 +1,7 @@ import type { ComposeSpecification } from "@dokploy/server"; import { addSuffixToServiceNames, generateRandomHash } from "@dokploy/server"; -import { load } from "js-yaml"; import { expect, test } from "vitest"; +import { parse } from "yaml"; test("Generate random hash with 8 characters", () => { const hash = generateRandomHash(); @@ -35,7 +35,7 @@ networks: `; test("Add suffix to service names with volumes_from in compose file", () => { - const composeData = load(composeFile3) as ComposeSpecification; + const composeData = parse(composeFile3) as ComposeSpecification; const suffix = generateRandomHash(); diff --git a/apps/dokploy/__test__/compose/volume/volume-2.test.ts b/apps/dokploy/__test__/compose/volume/volume-2.test.ts index 6aa9d01d3..7ffbc4c1a 100644 --- a/apps/dokploy/__test__/compose/volume/volume-2.test.ts +++ b/apps/dokploy/__test__/compose/volume/volume-2.test.ts @@ -4,8 +4,8 @@ import { addSuffixToVolumesRoot, generateRandomHash, } from "@dokploy/server"; -import { load } from "js-yaml"; import { expect, test } from "vitest"; +import { parse } from "yaml"; const composeFile = ` services: @@ -70,7 +70,7 @@ volumes: driver: local `; -const expectedDockerCompose = load(` +const expectedDockerCompose = parse(` services: mail: image: bytemark/smtp @@ -143,7 +143,7 @@ test("Generate random hash with 8 characters", () => { // Docker compose needs unique names for services, volumes, networks and containers // So base on a input which is a dockercompose file, it should replace the name with a hash and return a new dockercompose file test("Add suffix to volumes root property", () => { - const composeData = load(composeFile) as ComposeSpecification; + const composeData = parse(composeFile) as ComposeSpecification; const suffix = generateRandomHash(); @@ -165,7 +165,7 @@ test("Add suffix to volumes root property", () => { }); test("Expect to change the suffix in all the possible places", () => { - const composeData = load(composeFile) as ComposeSpecification; + const composeData = parse(composeFile) as ComposeSpecification; const suffix = "testhash"; const updatedComposeData = addSuffixToAllVolumes(composeData, suffix); @@ -195,7 +195,7 @@ volumes: mongo-data: `; -const expectedDockerCompose2 = load(` +const expectedDockerCompose2 = parse(` version: '3.8' services: app: @@ -218,7 +218,7 @@ volumes: `) as ComposeSpecification; test("Expect to change the suffix in all the possible places (2 Try)", () => { - const composeData = load(composeFile2) as ComposeSpecification; + const composeData = parse(composeFile2) as ComposeSpecification; const suffix = "testhash"; const updatedComposeData = addSuffixToAllVolumes(composeData, suffix); @@ -248,7 +248,7 @@ volumes: mongo-data: `; -const expectedDockerCompose3 = load(` +const expectedDockerCompose3 = parse(` version: '3.8' services: app: @@ -271,7 +271,7 @@ volumes: `) as ComposeSpecification; test("Expect to change the suffix in all the possible places (3 Try)", () => { - const composeData = load(composeFile3) as ComposeSpecification; + const composeData = parse(composeFile3) as ComposeSpecification; const suffix = "testhash"; const updatedComposeData = addSuffixToAllVolumes(composeData, suffix); @@ -645,7 +645,7 @@ volumes: db-config: `; -const expectedDockerComposeComplex = load(` +const expectedDockerComposeComplex = parse(` version: "3.8" services: studio: @@ -1012,7 +1012,7 @@ volumes: `); test("Expect to change the suffix in all the possible places (4 Try)", () => { - const composeData = load(composeFileComplex) as ComposeSpecification; + const composeData = parse(composeFileComplex) as ComposeSpecification; const suffix = "testhash"; const updatedComposeData = addSuffixToAllVolumes(composeData, suffix); @@ -1065,7 +1065,7 @@ volumes: db-data: `; -const expectedDockerComposeExample1 = load(` +const expectedDockerComposeExample1 = parse(` version: "3.8" services: web: @@ -1111,7 +1111,7 @@ volumes: `) as ComposeSpecification; test("Expect to change the suffix in all the possible places (5 Try)", () => { - const composeData = load(composeFileExample1) as ComposeSpecification; + const composeData = parse(composeFileExample1) as ComposeSpecification; const suffix = "testhash"; const updatedComposeData = addSuffixToAllVolumes(composeData, suffix); @@ -1143,7 +1143,7 @@ volumes: backrest-cache: `; -const expectedDockerComposeBackrest = load(` +const expectedDockerComposeBackrest = parse(` services: backrest: image: garethgeorge/backrest:v1.7.3 @@ -1168,7 +1168,7 @@ volumes: `) as ComposeSpecification; test("Should handle volume paths with subdirectories correctly", () => { - const composeData = load(composeFileBackrest) as ComposeSpecification; + const composeData = parse(composeFileBackrest) as ComposeSpecification; const suffix = "testhash"; const updatedComposeData = addSuffixToAllVolumes(composeData, suffix); diff --git a/apps/dokploy/__test__/compose/volume/volume-root.test.ts b/apps/dokploy/__test__/compose/volume/volume-root.test.ts index 80db1f0cc..69afb7f99 100644 --- a/apps/dokploy/__test__/compose/volume/volume-root.test.ts +++ b/apps/dokploy/__test__/compose/volume/volume-root.test.ts @@ -1,7 +1,7 @@ import type { ComposeSpecification } from "@dokploy/server"; import { addSuffixToVolumesRoot, generateRandomHash } from "@dokploy/server"; -import { load } from "js-yaml"; import { expect, test } from "vitest"; +import { parse } from "yaml"; const composeFile = ` version: "3.8" @@ -29,7 +29,7 @@ test("Generate random hash with 8 characters", () => { }); test("Add suffix to volumes in root property", () => { - const composeData = load(composeFile) as ComposeSpecification; + const composeData = parse(composeFile) as ComposeSpecification; const suffix = generateRandomHash(); @@ -67,7 +67,7 @@ networks: `; test("Add suffix to volumes in root property (Case 2)", () => { - const composeData = load(composeFile2) as ComposeSpecification; + const composeData = parse(composeFile2) as ComposeSpecification; const suffix = generateRandomHash(); @@ -101,7 +101,7 @@ networks: `; test("Add suffix to volumes in root property (Case 3)", () => { - const composeData = load(composeFile3) as ComposeSpecification; + const composeData = parse(composeFile3) as ComposeSpecification; const suffix = generateRandomHash(); @@ -148,7 +148,7 @@ volumes: `; // Expected compose file con el prefijo `testhash` -const expectedComposeFile4 = load(` +const expectedComposeFile4 = parse(` version: "3.8" services: @@ -179,7 +179,7 @@ volumes: `) as ComposeSpecification; test("Add suffix to volumes in root property", () => { - const composeData = load(composeFile4) as ComposeSpecification; + const composeData = parse(composeFile4) as ComposeSpecification; const suffix = "testhash"; diff --git a/apps/dokploy/__test__/compose/volume/volume-services.test.ts b/apps/dokploy/__test__/compose/volume/volume-services.test.ts index 0e9cb018f..a42ab5fa9 100644 --- a/apps/dokploy/__test__/compose/volume/volume-services.test.ts +++ b/apps/dokploy/__test__/compose/volume/volume-services.test.ts @@ -3,8 +3,8 @@ import { addSuffixToVolumesInServices, generateRandomHash, } from "@dokploy/server"; -import { load } from "js-yaml"; import { expect, test } from "vitest"; +import { parse } from "yaml"; test("Generate random hash with 8 characters", () => { const hash = generateRandomHash(); @@ -24,7 +24,7 @@ services: `; test("Add suffix to volumes declared directly in services", () => { - const composeData = load(composeFile1) as ComposeSpecification; + const composeData = parse(composeFile1) as ComposeSpecification; const suffix = generateRandomHash(); @@ -59,7 +59,7 @@ volumes: `; test("Add suffix to volumes declared directly in services (Case 2)", () => { - const composeData = load(composeFileTypeVolume) as ComposeSpecification; + const composeData = parse(composeFileTypeVolume) as ComposeSpecification; const suffix = generateRandomHash(); diff --git a/apps/dokploy/__test__/compose/volume/volume.test.ts b/apps/dokploy/__test__/compose/volume/volume.test.ts index 6f8e76708..2ccd12da6 100644 --- a/apps/dokploy/__test__/compose/volume/volume.test.ts +++ b/apps/dokploy/__test__/compose/volume/volume.test.ts @@ -1,7 +1,7 @@ import type { ComposeSpecification } from "@dokploy/server"; import { addSuffixToAllVolumes } from "@dokploy/server"; -import { load } from "js-yaml"; import { expect, test } from "vitest"; +import { parse } from "yaml"; const composeFileTypeVolume = ` version: "3.8" @@ -23,7 +23,7 @@ volumes: driver: local `; -const expectedComposeFileTypeVolume = load(` +const expectedComposeFileTypeVolume = parse(` version: "3.8" services: @@ -44,7 +44,7 @@ volumes: `) as ComposeSpecification; test("Add suffix to volumes with type: volume in services", () => { - const composeData = load(composeFileTypeVolume) as ComposeSpecification; + const composeData = parse(composeFileTypeVolume) as ComposeSpecification; const suffix = "testhash"; @@ -73,7 +73,7 @@ volumes: driver: local `; -const expectedComposeFileTypeVolume1 = load(` +const expectedComposeFileTypeVolume1 = parse(` version: "3.8" services: @@ -93,7 +93,7 @@ volumes: `) as ComposeSpecification; test("Add suffix to mixed volumes in services", () => { - const composeData = load(composeFileTypeVolume1) as ComposeSpecification; + const composeData = parse(composeFileTypeVolume1) as ComposeSpecification; const suffix = "testhash"; @@ -128,7 +128,7 @@ volumes: device: /path/to/app/logs `; -const expectedComposeFileTypeVolume2 = load(` +const expectedComposeFileTypeVolume2 = parse(` version: "3.8" services: @@ -154,7 +154,7 @@ volumes: `) as ComposeSpecification; test("Add suffix to complex volume configurations in services", () => { - const composeData = load(composeFileTypeVolume2) as ComposeSpecification; + const composeData = parse(composeFileTypeVolume2) as ComposeSpecification; const suffix = "testhash"; @@ -218,7 +218,7 @@ volumes: device: /path/to/shared/logs `; -const expectedComposeFileTypeVolume3 = load(` +const expectedComposeFileTypeVolume3 = parse(` version: "3.8" services: @@ -273,7 +273,7 @@ volumes: `) as ComposeSpecification; test("Add suffix to complex nested volumes configuration in services", () => { - const composeData = load(composeFileTypeVolume3) as ComposeSpecification; + const composeData = parse(composeFileTypeVolume3) as ComposeSpecification; const suffix = "testhash"; diff --git a/apps/dokploy/components/dashboard/application/advanced/traefik/update-traefik-config.tsx b/apps/dokploy/components/dashboard/application/advanced/traefik/update-traefik-config.tsx index 73512837f..bf3d5d9bc 100644 --- a/apps/dokploy/components/dashboard/application/advanced/traefik/update-traefik-config.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/traefik/update-traefik-config.tsx @@ -1,8 +1,8 @@ import { zodResolver } from "@hookform/resolvers/zod"; -import jsyaml from "js-yaml"; import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; +import { parse, stringify, YAMLParseError } from "yaml"; import { z } from "zod"; import { AlertBlock } from "@/components/shared/alert-block"; import { CodeEditor } from "@/components/shared/code-editor"; @@ -38,11 +38,11 @@ interface Props { export const validateAndFormatYAML = (yamlText: string) => { try { - const obj = jsyaml.load(yamlText); - const formattedYaml = jsyaml.dump(obj, { indent: 4 }); + const obj = parse(yamlText); + const formattedYaml = stringify(obj, { indent: 4 }); return { valid: true, formattedYaml, error: null }; } catch (error) { - if (error instanceof jsyaml.YAMLException) { + if (error instanceof YAMLParseError) { return { valid: false, formattedYaml: yamlText, @@ -89,7 +89,7 @@ export const UpdateTraefikConfig = ({ applicationId }: Props) => { if (!valid) { form.setError("traefikConfig", { type: "manual", - message: error || "Invalid YAML", + message: (error as string) || "Invalid YAML", }); return; } diff --git a/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx b/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx index 13694a283..1045856c2 100644 --- a/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx +++ b/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx @@ -1,6 +1,7 @@ import { Clock, Loader2, RefreshCcw, RocketIcon, Settings } from "lucide-react"; -import React, { useEffect, useState } from "react"; +import React, { useEffect, useMemo, useState } from "react"; import { toast } from "sonner"; +import { AlertBlock } from "@/components/shared/alert-block"; import { DateTooltip } from "@/components/shared/date-tooltip"; import { DialogAction } from "@/components/shared/dialog-action"; import { StatusTooltip } from "@/components/shared/status-tooltip"; @@ -61,12 +62,48 @@ export const ShowDeployments = ({ }, ); + const { data: isCloud } = api.settings.isCloud.useQuery(); + const { mutateAsync: rollback, isLoading: isRollingBack } = api.rollback.rollback.useMutation(); const { mutateAsync: killProcess, isLoading: isKillingProcess } = api.deployment.killProcess.useMutation(); + // Cancel deployment mutations + const { + mutateAsync: cancelApplicationDeployment, + isLoading: isCancellingApp, + } = api.application.cancelDeployment.useMutation(); + const { + mutateAsync: cancelComposeDeployment, + isLoading: isCancellingCompose, + } = api.compose.cancelDeployment.useMutation(); + const [url, setUrl] = React.useState(""); + + // Check for stuck deployment (more than 9 minutes) - only for the most recent deployment + const stuckDeployment = useMemo(() => { + if (!isCloud || !deployments || deployments.length === 0) return null; + + const now = Date.now(); + const NINE_MINUTES = 10 * 60 * 1000; // 9 minutes in milliseconds + + // Get the most recent deployment (first in the list since they're sorted by date) + const mostRecentDeployment = deployments[0]; + + if ( + !mostRecentDeployment || + mostRecentDeployment.status !== "running" || + !mostRecentDeployment.startedAt + ) { + return null; + } + + const startTime = new Date(mostRecentDeployment.startedAt).getTime(); + const elapsed = now - startTime; + + return elapsed > NINE_MINUTES ? mostRecentDeployment : null; + }, [isCloud, deployments]); useEffect(() => { setUrl(document.location.origin); }, []); @@ -94,6 +131,54 @@ export const ShowDeployments = ({ + {stuckDeployment && (type === "application" || type === "compose") && ( + +
+
+
+ Build appears to be stuck +
+

+ Hey! Looks like the build has been running for more than 10 + minutes. Would you like to cancel this deployment? +

+
+ +
+
+ )} {refreshToken && (
diff --git a/apps/dokploy/components/dashboard/mariadb/general/show-external-mariadb-credentials.tsx b/apps/dokploy/components/dashboard/mariadb/general/show-external-mariadb-credentials.tsx index 0b31d4962..8745db286 100644 --- a/apps/dokploy/components/dashboard/mariadb/general/show-external-mariadb-credentials.tsx +++ b/apps/dokploy/components/dashboard/mariadb/general/show-external-mariadb-credentials.tsx @@ -102,9 +102,9 @@ export const ShowExternalMariadbCredentials = ({ mariadbId }: Props) => { External Credentials - In order to make the database reachable trought internet is - required to set a port, make sure the port is not used by another - application or database + In order to make the database reachable through the internet, you + must set a port and ensure that the port is not being used by + another application or database diff --git a/apps/dokploy/components/dashboard/mongo/general/show-external-mongo-credentials.tsx b/apps/dokploy/components/dashboard/mongo/general/show-external-mongo-credentials.tsx index 51765de9f..d30061db5 100644 --- a/apps/dokploy/components/dashboard/mongo/general/show-external-mongo-credentials.tsx +++ b/apps/dokploy/components/dashboard/mongo/general/show-external-mongo-credentials.tsx @@ -102,9 +102,9 @@ export const ShowExternalMongoCredentials = ({ mongoId }: Props) => { External Credentials - In order to make the database reachable trought internet is - required to set a port, make sure the port is not used by another - application or database + In order to make the database reachable through the internet, you + must set a port and ensure that the port is not being used by + another application or database diff --git a/apps/dokploy/components/dashboard/mysql/general/show-external-mysql-credentials.tsx b/apps/dokploy/components/dashboard/mysql/general/show-external-mysql-credentials.tsx index a767b70c0..dfaa36f6b 100644 --- a/apps/dokploy/components/dashboard/mysql/general/show-external-mysql-credentials.tsx +++ b/apps/dokploy/components/dashboard/mysql/general/show-external-mysql-credentials.tsx @@ -102,9 +102,9 @@ export const ShowExternalMysqlCredentials = ({ mysqlId }: Props) => { External Credentials - In order to make the database reachable trought internet is - required to set a port, make sure the port is not used by another - application or database + In order to make the database reachable through the internet, you + must set a port and ensure that the port is not being used by + another application or database diff --git a/apps/dokploy/components/dashboard/postgres/general/show-external-postgres-credentials.tsx b/apps/dokploy/components/dashboard/postgres/general/show-external-postgres-credentials.tsx index 1e4842d13..46b3772a0 100644 --- a/apps/dokploy/components/dashboard/postgres/general/show-external-postgres-credentials.tsx +++ b/apps/dokploy/components/dashboard/postgres/general/show-external-postgres-credentials.tsx @@ -104,9 +104,9 @@ export const ShowExternalPostgresCredentials = ({ postgresId }: Props) => { External Credentials - In order to make the database reachable trought internet is - required to set a port, make sure the port is not used by another - application or database + In order to make the database reachable through the internet, you + must set a port and ensure that the port is not being used by + another application or database diff --git a/apps/dokploy/components/dashboard/project/add-application.tsx b/apps/dokploy/components/dashboard/project/add-application.tsx index dd4effb0f..079701eb8 100644 --- a/apps/dokploy/components/dashboard/project/add-application.tsx +++ b/apps/dokploy/components/dashboard/project/add-application.tsx @@ -76,6 +76,10 @@ export const AddApplication = ({ environmentId, projectName }: Props) => { const { data: servers } = api.server.withSSHKey.useQuery(); const hasServers = servers && servers.length > 0; + // Show dropdown logic based on cloud environment + // Cloud: show only if there are remote servers (no Dokploy option) + // Self-hosted: show only if there are remote servers (Dokploy is default, hide if no remote servers) + const shouldShowServerDropdown = hasServers; const { mutateAsync, isLoading, error, isError } = api.application.create.useMutation(); @@ -94,8 +98,8 @@ export const AddApplication = ({ environmentId, projectName }: Props) => { name: data.name, appName: data.appName, description: data.description, + serverId: data.serverId === "dokploy" ? undefined : data.serverId, environmentId, - serverId: data.serverId, }) .then(async () => { toast.success("Service Created"); @@ -157,7 +161,7 @@ export const AddApplication = ({ environmentId, projectName }: Props) => { )} /> - {hasServers && ( + {shouldShowServerDropdown && ( { diff --git a/apps/dokploy/components/dashboard/project/add-compose.tsx b/apps/dokploy/components/dashboard/project/add-compose.tsx index d565b5bfd..a187104ec 100644 --- a/apps/dokploy/components/dashboard/project/add-compose.tsx +++ b/apps/dokploy/components/dashboard/project/add-compose.tsx @@ -82,6 +82,10 @@ export const AddCompose = ({ environmentId, projectName }: Props) => { const { data: environment } = api.environment.one.useQuery({ environmentId }); const hasServers = servers && servers.length > 0; + // Show dropdown logic based on cloud environment + // Cloud: show only if there are remote servers (no Dokploy option) + // Self-hosted: show only if there are remote servers (Dokploy is default, hide if no remote servers) + const shouldShowServerDropdown = hasServers; const form = useForm({ defaultValues: { @@ -104,7 +108,7 @@ export const AddCompose = ({ environmentId, projectName }: Props) => { environmentId, composeType: data.composeType, appName: data.appName, - serverId: data.serverId, + serverId: data.serverId === "dokploy" ? undefined : data.serverId, }) .then(async () => { toast.success("Compose Created"); @@ -169,7 +173,7 @@ export const AddCompose = ({ environmentId, projectName }: Props) => { )} />
- {hasServers && ( + {shouldShowServerDropdown && ( { diff --git a/apps/dokploy/components/dashboard/project/add-database.tsx b/apps/dokploy/components/dashboard/project/add-database.tsx index c7d114c93..064e93544 100644 --- a/apps/dokploy/components/dashboard/project/add-database.tsx +++ b/apps/dokploy/components/dashboard/project/add-database.tsx @@ -179,6 +179,7 @@ export const AddDatabase = ({ environmentId, projectName }: Props) => { const utils = api.useUtils(); const [visible, setVisible] = useState(false); const slug = slugify(projectName); + const { data: isCloud } = api.settings.isCloud.useQuery(); const { data: servers } = api.server.withSSHKey.useQuery(); const postgresMutation = api.postgres.create.useMutation(); const mongoMutation = api.mongo.create.useMutation(); @@ -190,6 +191,10 @@ export const AddDatabase = ({ environmentId, projectName }: Props) => { const { data: environment } = api.environment.one.useQuery({ environmentId }); const hasServers = servers && servers.length > 0; + // Show dropdown logic based on cloud environment + // Cloud: show only if there are remote servers (no Dokploy option) + // Self-hosted: show only if there are remote servers (Dokploy is default, hide if no remote servers) + const shouldShowServerDropdown = hasServers; const form = useForm({ defaultValues: { @@ -223,9 +228,8 @@ export const AddDatabase = ({ environmentId, projectName }: Props) => { name: data.name, appName: data.appName, dockerImage: defaultDockerImage, - projectId: environment?.projectId || "", + serverId: data.serverId === "dokploy" ? undefined : data.serverId, environmentId, - serverId: data.serverId, description: data.description, }; @@ -237,7 +241,7 @@ export const AddDatabase = ({ environmentId, projectName }: Props) => { databaseUser: data.databaseUser || databasesUserDefaultPlaceholder[data.type], - serverId: data.serverId, + serverId: data.serverId === "dokploy" ? null : data.serverId, }); } else if (data.type === "mongo") { promise = mongoMutation.mutateAsync({ @@ -245,14 +249,14 @@ export const AddDatabase = ({ environmentId, projectName }: Props) => { databasePassword: data.databasePassword, databaseUser: data.databaseUser || databasesUserDefaultPlaceholder[data.type], - serverId: data.serverId, + serverId: data.serverId === "dokploy" ? null : data.serverId, replicaSets: data.replicaSets, }); } else if (data.type === "redis") { promise = redisMutation.mutateAsync({ ...commonParams, databasePassword: data.databasePassword, - serverId: data.serverId, + serverId: data.serverId === "dokploy" ? null : data.serverId, }); } else if (data.type === "mariadb") { promise = mariadbMutation.mutateAsync({ @@ -262,7 +266,7 @@ export const AddDatabase = ({ environmentId, projectName }: Props) => { databaseName: data.databaseName || "mariadb", databaseUser: data.databaseUser || databasesUserDefaultPlaceholder[data.type], - serverId: data.serverId, + serverId: data.serverId === "dokploy" ? null : data.serverId, }); } else if (data.type === "mysql") { promise = mysqlMutation.mutateAsync({ @@ -271,8 +275,8 @@ export const AddDatabase = ({ environmentId, projectName }: Props) => { databaseName: data.databaseName || "mysql", databaseUser: data.databaseUser || databasesUserDefaultPlaceholder[data.type], + serverId: data.serverId === "dokploy" ? null : data.serverId, databaseRootPassword: data.databaseRootPassword || "", - serverId: data.serverId, }); } @@ -403,7 +407,7 @@ export const AddDatabase = ({ environmentId, projectName }: Props) => { )} /> - {hasServers && ( + {shouldShowServerDropdown && ( { Select a Server setQuery(e.target.value)} - className="w-full sm:w-[200px]" + className="w-full" value={query} /> { onClick={() => setViewMode(viewMode === "detailed" ? "icon" : "detailed") } - className="h-9 w-9" + className="h-9 w-9 flex-shrink-0" > {viewMode === "detailed" ? ( @@ -430,7 +434,7 @@ export const AddTemplate = ({ environmentId, baseUrl }: Props) => { project. - {hasServers && ( + {shouldShowServerDropdown && (
@@ -459,12 +463,29 @@ export const AddTemplate = ({ environmentId, baseUrl }: Props) => { onValueChange={(e) => { setServerId(e); }} + defaultValue={ + !isCloud ? "dokploy" : undefined + } > - + + {!isCloud && ( + + + Dokploy + + Default + + + + )} {servers?.map((server) => ( { ))} - Servers ({servers?.length}) + Servers ( + {servers?.length + (!isCloud ? 1 : 0)}) @@ -493,8 +515,11 @@ export const AddTemplate = ({ environmentId, baseUrl }: Props) => { disabled={isLoading} onClick={async () => { const promise = mutateAsync({ + serverId: + serverId === "dokploy" + ? undefined + : serverId, environmentId, - serverId: serverId || undefined, id: template.id, baseUrl: customBaseUrl, }); diff --git a/apps/dokploy/components/dashboard/project/ai/step-one.tsx b/apps/dokploy/components/dashboard/project/ai/step-one.tsx index 29cf90305..2a7039f6c 100644 --- a/apps/dokploy/components/dashboard/project/ai/step-one.tsx +++ b/apps/dokploy/components/dashboard/project/ai/step-one.tsx @@ -25,7 +25,12 @@ const examples = [ export const StepOne = ({ setTemplateInfo, templateInfo }: any) => { // Get servers from the API const { data: servers } = api.server.withSSHKey.useQuery(); + const { data: isCloud } = api.settings.isCloud.useQuery(); const hasServers = servers && servers.length > 0; + // Show dropdown logic based on cloud environment + // Cloud: show only if there are remote servers (no Dokploy option) + // Self-hosted: show only if there are remote servers (Dokploy is default, hide if no remote servers) + const shouldShowServerDropdown = hasServers; const handleExampleClick = (example: string) => { setTemplateInfo({ ...templateInfo, userInput: example }); @@ -48,34 +53,58 @@ export const StepOne = ({ setTemplateInfo, templateInfo }: any) => { />
- {hasServers && ( + {shouldShowServerDropdown && (
diff --git a/apps/dokploy/components/dashboard/project/ai/step-three.tsx b/apps/dokploy/components/dashboard/project/ai/step-three.tsx index 188cd5257..bf074f3bd 100644 --- a/apps/dokploy/components/dashboard/project/ai/step-three.tsx +++ b/apps/dokploy/components/dashboard/project/ai/step-three.tsx @@ -88,7 +88,7 @@ export const StepThree = ({ templateInfo }: StepProps) => {

Configuration Files

    - {templateInfo?.details?.configFiles.map((file, index) => ( + {templateInfo?.details?.configFiles?.map((file, index) => (
  • {file.filePath} diff --git a/apps/dokploy/components/dashboard/project/ai/step-two.tsx b/apps/dokploy/components/dashboard/project/ai/step-two.tsx index e76e8ff76..09484bc57 100644 --- a/apps/dokploy/components/dashboard/project/ai/step-two.tsx +++ b/apps/dokploy/components/dashboard/project/ai/step-two.tsx @@ -1,5 +1,5 @@ -import { Bot, Eye, EyeOff, PlusCircle, Trash2 } from "lucide-react"; -import { useEffect, useState } from "react"; +import { Bot, PlusCircle, Trash2 } from "lucide-react"; +import { useEffect } from "react"; import ReactMarkdown from "react-markdown"; import { toast } from "sonner"; import { AlertBlock } from "@/components/shared/alert-block"; @@ -27,7 +27,6 @@ export interface StepProps { export const StepTwo = ({ templateInfo, setTemplateInfo }: StepProps) => { const suggestions = templateInfo.suggestions || []; const selectedVariant = templateInfo.details; - const [showValues, setShowValues] = useState>({}); const { mutateAsync, isLoading, error, isError } = api.ai.suggest.useMutation(); @@ -44,7 +43,7 @@ export const StepTwo = ({ templateInfo, setTemplateInfo }: StepProps) => { .then((data) => { setTemplateInfo({ ...templateInfo, - suggestions: data, + suggestions: data || [], }); }) .catch((error) => { @@ -54,10 +53,6 @@ export const StepTwo = ({ templateInfo, setTemplateInfo }: StepProps) => { }); }, [templateInfo.userInput]); - const toggleShowValue = (name: string) => { - setShowValues((prev) => ({ ...prev, [name]: !prev[name] })); - }; - const handleEnvVariableChange = ( index: number, field: "name" | "value", @@ -308,11 +303,9 @@ export const StepTwo = ({ templateInfo, setTemplateInfo }: StepProps) => { placeholder="Variable Name" className="flex-1" /> -
    +
    handleEnvVariableChange( @@ -323,19 +316,6 @@ export const StepTwo = ({ templateInfo, setTemplateInfo }: StepProps) => { } placeholder="Variable Value" /> -
@@ -1024,7 +1149,8 @@ export const HandleNotifications = ({ notificationId }: Props) => { isLoadingTelegram || isLoadingDiscord || isLoadingEmail || - isLoadingGotify + isLoadingGotify || + isLoadingNtfy } variant="secondary" onClick={async () => { @@ -1061,6 +1187,13 @@ export const HandleNotifications = ({ notificationId }: Props) => { priority: form.getValues("priority"), decoration: form.getValues("decoration"), }); + } else if (type === "ntfy") { + await testNtfyConnection({ + serverUrl: form.getValues("serverUrl"), + topic: form.getValues("topic"), + accessToken: form.getValues("accessToken"), + priority: form.getValues("priority"), + }); } toast.success("Connection Success"); } catch { diff --git a/apps/dokploy/components/dashboard/settings/notifications/show-notifications.tsx b/apps/dokploy/components/dashboard/settings/notifications/show-notifications.tsx index f6d31c7d7..fe31acc4c 100644 --- a/apps/dokploy/components/dashboard/settings/notifications/show-notifications.tsx +++ b/apps/dokploy/components/dashboard/settings/notifications/show-notifications.tsx @@ -88,6 +88,11 @@ export const ShowNotifications = () => {
)} + {notification.notificationType === "ntfy" && ( +
+ +
+ )} {notification.name} diff --git a/apps/dokploy/components/dashboard/settings/profile/profile-form.tsx b/apps/dokploy/components/dashboard/settings/profile/profile-form.tsx index 7ac65f1b2..d040472d6 100644 --- a/apps/dokploy/components/dashboard/settings/profile/profile-form.tsx +++ b/apps/dokploy/components/dashboard/settings/profile/profile-form.tsx @@ -33,7 +33,10 @@ import { Disable2FA } from "./disable-2fa"; import { Enable2FA } from "./enable-2fa"; const profileSchema = z.object({ - email: z.string(), + email: z + .string() + .email("Please enter a valid email address") + .min(1, "Email is required"), password: z.string().nullable(), currentPassword: z.string().nullable(), image: z.string().optional(), diff --git a/apps/dokploy/components/dashboard/settings/servers/actions/show-traefik-actions.tsx b/apps/dokploy/components/dashboard/settings/servers/actions/show-traefik-actions.tsx index 38039cc6b..d9573ca74 100644 --- a/apps/dokploy/components/dashboard/settings/servers/actions/show-traefik-actions.tsx +++ b/apps/dokploy/components/dashboard/settings/servers/actions/show-traefik-actions.tsx @@ -97,11 +97,7 @@ export const ShowTraefikActions = ({ serverId }: Props) => { ); refetchDashboard(); }) - .catch(() => { - toast.error( - `${haveTraefikDashboardPortEnabled ? "Disabled" : "Enabled"} Dashboard`, - ); - }); + .catch(() => {}); }} className="w-full cursor-pointer space-x-3" > diff --git a/apps/dokploy/components/shared/focus-shortcut-input.tsx b/apps/dokploy/components/shared/focus-shortcut-input.tsx new file mode 100644 index 000000000..9c9215236 --- /dev/null +++ b/apps/dokploy/components/shared/focus-shortcut-input.tsx @@ -0,0 +1,36 @@ +import { useEffect, useRef } from "react"; +import { Input } from "@/components/ui/input"; + +type Props = React.ComponentPropsWithoutRef; + +export const FocusShortcutInput = (props: Props) => { + const inputRef = useRef(null); + + useEffect(() => { + const onKeyDown = (e: KeyboardEvent) => { + const isMod = e.metaKey || e.ctrlKey; + if (!isMod || e.key.toLowerCase() !== "k") return; + + const target = e.target as HTMLElement | null; + if (target) { + const tag = target.tagName; + if ( + target.isContentEditable || + tag === "INPUT" || + tag === "TEXTAREA" || + tag === "SELECT" || + target.getAttribute("role") === "textbox" + ) + return; + } + + e.preventDefault(); + inputRef.current?.focus(); + }; + + window.addEventListener("keydown", onKeyDown); + return () => window.removeEventListener("keydown", onKeyDown); + }, []); + + return ; +}; diff --git a/apps/dokploy/drizzle/0110_red_psynapse.sql b/apps/dokploy/drizzle/0110_red_psynapse.sql new file mode 100644 index 000000000..a876db72f --- /dev/null +++ b/apps/dokploy/drizzle/0110_red_psynapse.sql @@ -0,0 +1,11 @@ +ALTER TYPE "public"."notificationType" ADD VALUE 'ntfy';--> statement-breakpoint +CREATE TABLE "ntfy" ( + "ntfyId" text PRIMARY KEY NOT NULL, + "serverUrl" text NOT NULL, + "topic" text NOT NULL, + "accessToken" text NOT NULL, + "priority" integer DEFAULT 3 NOT NULL +); +--> statement-breakpoint +ALTER TABLE "notification" ADD COLUMN "ntfyId" text;--> statement-breakpoint +ALTER TABLE "notification" ADD CONSTRAINT "notification_ntfyId_ntfy_ntfyId_fk" FOREIGN KEY ("ntfyId") REFERENCES "public"."ntfy"("ntfyId") ON DELETE cascade ON UPDATE no action; \ No newline at end of file diff --git a/apps/dokploy/drizzle/meta/0110_snapshot.json b/apps/dokploy/drizzle/meta/0110_snapshot.json new file mode 100644 index 000000000..e855d8abb --- /dev/null +++ b/apps/dokploy/drizzle/meta/0110_snapshot.json @@ -0,0 +1,6559 @@ +{ + "id": "9f9b4142-e739-4c21-8618-676d62e9b5ae", + "prevId": "b5e88b4f-1396-4456-b338-65c93e2194fa", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is2FAEnabled": { + "name": "is2FAEnabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "resetPasswordToken": { + "name": "resetPasswordToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resetPasswordExpiresAt": { + "name": "resetPasswordExpiresAt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "confirmationToken": { + "name": "confirmationToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "confirmationExpiresAt": { + "name": "confirmationExpiresAt", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "account_user_id_user_temp_id_fk": { + "name": "account_user_id_user_temp_id_fk", + "tableFrom": "account", + "tableTo": "user_temp", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.apikey": { + "name": "apikey", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "start": { + "name": "start", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prefix": { + "name": "prefix", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "refill_interval": { + "name": "refill_interval", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "refill_amount": { + "name": "refill_amount", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_refill_at": { + "name": "last_refill_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "rate_limit_enabled": { + "name": "rate_limit_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "rate_limit_time_window": { + "name": "rate_limit_time_window", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "rate_limit_max": { + "name": "rate_limit_max", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "request_count": { + "name": "request_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "remaining": { + "name": "remaining", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_request": { + "name": "last_request", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "permissions": { + "name": "permissions", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "apikey_user_id_user_temp_id_fk": { + "name": "apikey_user_id_user_temp_id_fk", + "tableFrom": "apikey", + "tableTo": "user_temp", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invitation": { + "name": "invitation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "inviter_id": { + "name": "inviter_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "invitation_organization_id_organization_id_fk": { + "name": "invitation_organization_id_organization_id_fk", + "tableFrom": "invitation", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invitation_inviter_id_user_temp_id_fk": { + "name": "invitation_inviter_id_user_temp_id_fk", + "tableFrom": "invitation", + "tableTo": "user_temp", + "columnsFrom": [ + "inviter_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.member": { + "name": "member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "canCreateProjects": { + "name": "canCreateProjects", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canAccessToSSHKeys": { + "name": "canAccessToSSHKeys", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canCreateServices": { + "name": "canCreateServices", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canDeleteProjects": { + "name": "canDeleteProjects", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canDeleteServices": { + "name": "canDeleteServices", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canAccessToDocker": { + "name": "canAccessToDocker", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canAccessToAPI": { + "name": "canAccessToAPI", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canAccessToGitProviders": { + "name": "canAccessToGitProviders", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canAccessToTraefikFiles": { + "name": "canAccessToTraefikFiles", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "accesedProjects": { + "name": "accesedProjects", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "ARRAY[]::text[]" + }, + "accessedEnvironments": { + "name": "accessedEnvironments", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "ARRAY[]::text[]" + }, + "accesedServices": { + "name": "accesedServices", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "ARRAY[]::text[]" + } + }, + "indexes": {}, + "foreignKeys": { + "member_organization_id_organization_id_fk": { + "name": "member_organization_id_organization_id_fk", + "tableFrom": "member", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "member_user_id_user_temp_id_fk": { + "name": "member_user_id_user_temp_id_fk", + "tableFrom": "member", + "tableTo": "user_temp", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization": { + "name": "organization", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "logo": { + "name": "logo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "organization_owner_id_user_temp_id_fk": { + "name": "organization_owner_id_user_temp_id_fk", + "tableFrom": "organization", + "tableTo": "user_temp", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "organization_slug_unique": { + "name": "organization_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.two_factor": { + "name": "two_factor", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "backup_codes": { + "name": "backup_codes", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "two_factor_user_id_user_temp_id_fk": { + "name": "two_factor_user_id_user_temp_id_fk", + "tableFrom": "two_factor", + "tableTo": "user_temp", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ai": { + "name": "ai", + "schema": "", + "columns": { + "aiId": { + "name": "aiId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "apiUrl": { + "name": "apiUrl", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "apiKey": { + "name": "apiKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "isEnabled": { + "name": "isEnabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "ai_organizationId_organization_id_fk": { + "name": "ai_organizationId_organization_id_fk", + "tableFrom": "ai", + "tableTo": "organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.application": { + "name": "application", + "schema": "", + "columns": { + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previewEnv": { + "name": "previewEnv", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "watchPaths": { + "name": "watchPaths", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "previewBuildArgs": { + "name": "previewBuildArgs", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previewLabels": { + "name": "previewLabels", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "previewWildcard": { + "name": "previewWildcard", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previewPort": { + "name": "previewPort", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 3000 + }, + "previewHttps": { + "name": "previewHttps", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "previewPath": { + "name": "previewPath", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "certificateType": { + "name": "certificateType", + "type": "certificateType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "previewCustomCertResolver": { + "name": "previewCustomCertResolver", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previewLimit": { + "name": "previewLimit", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 3 + }, + "isPreviewDeploymentsActive": { + "name": "isPreviewDeploymentsActive", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "previewRequireCollaboratorPermissions": { + "name": "previewRequireCollaboratorPermissions", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "rollbackActive": { + "name": "rollbackActive", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "buildArgs": { + "name": "buildArgs", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "subtitle": { + "name": "subtitle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refreshToken": { + "name": "refreshToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sourceType": { + "name": "sourceType", + "type": "sourceType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'github'" + }, + "cleanCache": { + "name": "cleanCache", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "repository": { + "name": "repository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "buildPath": { + "name": "buildPath", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "triggerType": { + "name": "triggerType", + "type": "triggerType", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'push'" + }, + "autoDeploy": { + "name": "autoDeploy", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "gitlabProjectId": { + "name": "gitlabProjectId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "gitlabRepository": { + "name": "gitlabRepository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabOwner": { + "name": "gitlabOwner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabBranch": { + "name": "gitlabBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabBuildPath": { + "name": "gitlabBuildPath", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "gitlabPathNamespace": { + "name": "gitlabPathNamespace", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "giteaRepository": { + "name": "giteaRepository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "giteaOwner": { + "name": "giteaOwner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "giteaBranch": { + "name": "giteaBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "giteaBuildPath": { + "name": "giteaBuildPath", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "bitbucketRepository": { + "name": "bitbucketRepository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketOwner": { + "name": "bitbucketOwner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketBranch": { + "name": "bitbucketBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketBuildPath": { + "name": "bitbucketBuildPath", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "registryUrl": { + "name": "registryUrl", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitUrl": { + "name": "customGitUrl", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitBranch": { + "name": "customGitBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitBuildPath": { + "name": "customGitBuildPath", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitSSHKeyId": { + "name": "customGitSSHKeyId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enableSubmodules": { + "name": "enableSubmodules", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "dockerfile": { + "name": "dockerfile", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dockerContextPath": { + "name": "dockerContextPath", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dockerBuildStage": { + "name": "dockerBuildStage", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dropBuildPath": { + "name": "dropBuildPath", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "healthCheckSwarm": { + "name": "healthCheckSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "restartPolicySwarm": { + "name": "restartPolicySwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "placementSwarm": { + "name": "placementSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "updateConfigSwarm": { + "name": "updateConfigSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "rollbackConfigSwarm": { + "name": "rollbackConfigSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "modeSwarm": { + "name": "modeSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "labelsSwarm": { + "name": "labelsSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "networkSwarm": { + "name": "networkSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "replicas": { + "name": "replicas", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "buildType": { + "name": "buildType", + "type": "buildType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'nixpacks'" + }, + "railpackVersion": { + "name": "railpackVersion", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'0.2.2'" + }, + "herokuVersion": { + "name": "herokuVersion", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'24'" + }, + "publishDirectory": { + "name": "publishDirectory", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "isStaticSpa": { + "name": "isStaticSpa", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "registryId": { + "name": "registryId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "environmentId": { + "name": "environmentId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "githubId": { + "name": "githubId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabId": { + "name": "gitlabId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "giteaId": { + "name": "giteaId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketId": { + "name": "bitbucketId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "application_customGitSSHKeyId_ssh-key_sshKeyId_fk": { + "name": "application_customGitSSHKeyId_ssh-key_sshKeyId_fk", + "tableFrom": "application", + "tableTo": "ssh-key", + "columnsFrom": [ + "customGitSSHKeyId" + ], + "columnsTo": [ + "sshKeyId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_registryId_registry_registryId_fk": { + "name": "application_registryId_registry_registryId_fk", + "tableFrom": "application", + "tableTo": "registry", + "columnsFrom": [ + "registryId" + ], + "columnsTo": [ + "registryId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_environmentId_environment_environmentId_fk": { + "name": "application_environmentId_environment_environmentId_fk", + "tableFrom": "application", + "tableTo": "environment", + "columnsFrom": [ + "environmentId" + ], + "columnsTo": [ + "environmentId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "application_githubId_github_githubId_fk": { + "name": "application_githubId_github_githubId_fk", + "tableFrom": "application", + "tableTo": "github", + "columnsFrom": [ + "githubId" + ], + "columnsTo": [ + "githubId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_gitlabId_gitlab_gitlabId_fk": { + "name": "application_gitlabId_gitlab_gitlabId_fk", + "tableFrom": "application", + "tableTo": "gitlab", + "columnsFrom": [ + "gitlabId" + ], + "columnsTo": [ + "gitlabId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_giteaId_gitea_giteaId_fk": { + "name": "application_giteaId_gitea_giteaId_fk", + "tableFrom": "application", + "tableTo": "gitea", + "columnsFrom": [ + "giteaId" + ], + "columnsTo": [ + "giteaId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_bitbucketId_bitbucket_bitbucketId_fk": { + "name": "application_bitbucketId_bitbucket_bitbucketId_fk", + "tableFrom": "application", + "tableTo": "bitbucket", + "columnsFrom": [ + "bitbucketId" + ], + "columnsTo": [ + "bitbucketId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_serverId_server_serverId_fk": { + "name": "application_serverId_server_serverId_fk", + "tableFrom": "application", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "application_appName_unique": { + "name": "application_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.backup": { + "name": "backup", + "schema": "", + "columns": { + "backupId": { + "name": "backupId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schedule": { + "name": "schedule", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "database": { + "name": "database", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "prefix": { + "name": "prefix", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serviceName": { + "name": "serviceName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "destinationId": { + "name": "destinationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "keepLatestCount": { + "name": "keepLatestCount", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "backupType": { + "name": "backupType", + "type": "backupType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'database'" + }, + "databaseType": { + "name": "databaseType", + "type": "databaseType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "postgresId": { + "name": "postgresId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mariadbId": { + "name": "mariadbId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mysqlId": { + "name": "mysqlId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mongoId": { + "name": "mongoId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "backup_destinationId_destination_destinationId_fk": { + "name": "backup_destinationId_destination_destinationId_fk", + "tableFrom": "backup", + "tableTo": "destination", + "columnsFrom": [ + "destinationId" + ], + "columnsTo": [ + "destinationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_composeId_compose_composeId_fk": { + "name": "backup_composeId_compose_composeId_fk", + "tableFrom": "backup", + "tableTo": "compose", + "columnsFrom": [ + "composeId" + ], + "columnsTo": [ + "composeId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_postgresId_postgres_postgresId_fk": { + "name": "backup_postgresId_postgres_postgresId_fk", + "tableFrom": "backup", + "tableTo": "postgres", + "columnsFrom": [ + "postgresId" + ], + "columnsTo": [ + "postgresId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_mariadbId_mariadb_mariadbId_fk": { + "name": "backup_mariadbId_mariadb_mariadbId_fk", + "tableFrom": "backup", + "tableTo": "mariadb", + "columnsFrom": [ + "mariadbId" + ], + "columnsTo": [ + "mariadbId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_mysqlId_mysql_mysqlId_fk": { + "name": "backup_mysqlId_mysql_mysqlId_fk", + "tableFrom": "backup", + "tableTo": "mysql", + "columnsFrom": [ + "mysqlId" + ], + "columnsTo": [ + "mysqlId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_mongoId_mongo_mongoId_fk": { + "name": "backup_mongoId_mongo_mongoId_fk", + "tableFrom": "backup", + "tableTo": "mongo", + "columnsFrom": [ + "mongoId" + ], + "columnsTo": [ + "mongoId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_userId_user_temp_id_fk": { + "name": "backup_userId_user_temp_id_fk", + "tableFrom": "backup", + "tableTo": "user_temp", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "backup_appName_unique": { + "name": "backup_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.bitbucket": { + "name": "bitbucket", + "schema": "", + "columns": { + "bitbucketId": { + "name": "bitbucketId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "bitbucketUsername": { + "name": "bitbucketUsername", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "appPassword": { + "name": "appPassword", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketWorkspaceName": { + "name": "bitbucketWorkspaceName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitProviderId": { + "name": "gitProviderId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "bitbucket_gitProviderId_git_provider_gitProviderId_fk": { + "name": "bitbucket_gitProviderId_git_provider_gitProviderId_fk", + "tableFrom": "bitbucket", + "tableTo": "git_provider", + "columnsFrom": [ + "gitProviderId" + ], + "columnsTo": [ + "gitProviderId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.certificate": { + "name": "certificate", + "schema": "", + "columns": { + "certificateId": { + "name": "certificateId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "certificateData": { + "name": "certificateData", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "privateKey": { + "name": "privateKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "certificatePath": { + "name": "certificatePath", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "autoRenew": { + "name": "autoRenew", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "certificate_organizationId_organization_id_fk": { + "name": "certificate_organizationId_organization_id_fk", + "tableFrom": "certificate", + "tableTo": "organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "certificate_serverId_server_serverId_fk": { + "name": "certificate_serverId_server_serverId_fk", + "tableFrom": "certificate", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "certificate_certificatePath_unique": { + "name": "certificate_certificatePath_unique", + "nullsNotDistinct": false, + "columns": [ + "certificatePath" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.compose": { + "name": "compose", + "schema": "", + "columns": { + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "composeFile": { + "name": "composeFile", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "refreshToken": { + "name": "refreshToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sourceType": { + "name": "sourceType", + "type": "sourceTypeCompose", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'github'" + }, + "composeType": { + "name": "composeType", + "type": "composeType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'docker-compose'" + }, + "repository": { + "name": "repository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "autoDeploy": { + "name": "autoDeploy", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "gitlabProjectId": { + "name": "gitlabProjectId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "gitlabRepository": { + "name": "gitlabRepository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabOwner": { + "name": "gitlabOwner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabBranch": { + "name": "gitlabBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabPathNamespace": { + "name": "gitlabPathNamespace", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketRepository": { + "name": "bitbucketRepository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketOwner": { + "name": "bitbucketOwner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketBranch": { + "name": "bitbucketBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "giteaRepository": { + "name": "giteaRepository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "giteaOwner": { + "name": "giteaOwner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "giteaBranch": { + "name": "giteaBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitUrl": { + "name": "customGitUrl", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitBranch": { + "name": "customGitBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitSSHKeyId": { + "name": "customGitSSHKeyId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "enableSubmodules": { + "name": "enableSubmodules", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "composePath": { + "name": "composePath", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'./docker-compose.yml'" + }, + "suffix": { + "name": "suffix", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "randomize": { + "name": "randomize", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "isolatedDeployment": { + "name": "isolatedDeployment", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "isolatedDeploymentsVolume": { + "name": "isolatedDeploymentsVolume", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "triggerType": { + "name": "triggerType", + "type": "triggerType", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'push'" + }, + "composeStatus": { + "name": "composeStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "environmentId": { + "name": "environmentId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "watchPaths": { + "name": "watchPaths", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "githubId": { + "name": "githubId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabId": { + "name": "gitlabId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketId": { + "name": "bitbucketId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "giteaId": { + "name": "giteaId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "compose_customGitSSHKeyId_ssh-key_sshKeyId_fk": { + "name": "compose_customGitSSHKeyId_ssh-key_sshKeyId_fk", + "tableFrom": "compose", + "tableTo": "ssh-key", + "columnsFrom": [ + "customGitSSHKeyId" + ], + "columnsTo": [ + "sshKeyId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "compose_environmentId_environment_environmentId_fk": { + "name": "compose_environmentId_environment_environmentId_fk", + "tableFrom": "compose", + "tableTo": "environment", + "columnsFrom": [ + "environmentId" + ], + "columnsTo": [ + "environmentId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "compose_githubId_github_githubId_fk": { + "name": "compose_githubId_github_githubId_fk", + "tableFrom": "compose", + "tableTo": "github", + "columnsFrom": [ + "githubId" + ], + "columnsTo": [ + "githubId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "compose_gitlabId_gitlab_gitlabId_fk": { + "name": "compose_gitlabId_gitlab_gitlabId_fk", + "tableFrom": "compose", + "tableTo": "gitlab", + "columnsFrom": [ + "gitlabId" + ], + "columnsTo": [ + "gitlabId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "compose_bitbucketId_bitbucket_bitbucketId_fk": { + "name": "compose_bitbucketId_bitbucket_bitbucketId_fk", + "tableFrom": "compose", + "tableTo": "bitbucket", + "columnsFrom": [ + "bitbucketId" + ], + "columnsTo": [ + "bitbucketId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "compose_giteaId_gitea_giteaId_fk": { + "name": "compose_giteaId_gitea_giteaId_fk", + "tableFrom": "compose", + "tableTo": "gitea", + "columnsFrom": [ + "giteaId" + ], + "columnsTo": [ + "giteaId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "compose_serverId_server_serverId_fk": { + "name": "compose_serverId_server_serverId_fk", + "tableFrom": "compose", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.deployment": { + "name": "deployment", + "schema": "", + "columns": { + "deploymentId": { + "name": "deploymentId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "deploymentStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'running'" + }, + "logPath": { + "name": "logPath", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pid": { + "name": "pid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "isPreviewDeployment": { + "name": "isPreviewDeployment", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "previewDeploymentId": { + "name": "previewDeploymentId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "startedAt": { + "name": "startedAt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "finishedAt": { + "name": "finishedAt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "errorMessage": { + "name": "errorMessage", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scheduleId": { + "name": "scheduleId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "backupId": { + "name": "backupId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rollbackId": { + "name": "rollbackId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "volumeBackupId": { + "name": "volumeBackupId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "deployment_applicationId_application_applicationId_fk": { + "name": "deployment_applicationId_application_applicationId_fk", + "tableFrom": "deployment", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "deployment_composeId_compose_composeId_fk": { + "name": "deployment_composeId_compose_composeId_fk", + "tableFrom": "deployment", + "tableTo": "compose", + "columnsFrom": [ + "composeId" + ], + "columnsTo": [ + "composeId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "deployment_serverId_server_serverId_fk": { + "name": "deployment_serverId_server_serverId_fk", + "tableFrom": "deployment", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "deployment_previewDeploymentId_preview_deployments_previewDeploymentId_fk": { + "name": "deployment_previewDeploymentId_preview_deployments_previewDeploymentId_fk", + "tableFrom": "deployment", + "tableTo": "preview_deployments", + "columnsFrom": [ + "previewDeploymentId" + ], + "columnsTo": [ + "previewDeploymentId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "deployment_scheduleId_schedule_scheduleId_fk": { + "name": "deployment_scheduleId_schedule_scheduleId_fk", + "tableFrom": "deployment", + "tableTo": "schedule", + "columnsFrom": [ + "scheduleId" + ], + "columnsTo": [ + "scheduleId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "deployment_backupId_backup_backupId_fk": { + "name": "deployment_backupId_backup_backupId_fk", + "tableFrom": "deployment", + "tableTo": "backup", + "columnsFrom": [ + "backupId" + ], + "columnsTo": [ + "backupId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "deployment_rollbackId_rollback_rollbackId_fk": { + "name": "deployment_rollbackId_rollback_rollbackId_fk", + "tableFrom": "deployment", + "tableTo": "rollback", + "columnsFrom": [ + "rollbackId" + ], + "columnsTo": [ + "rollbackId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "deployment_volumeBackupId_volume_backup_volumeBackupId_fk": { + "name": "deployment_volumeBackupId_volume_backup_volumeBackupId_fk", + "tableFrom": "deployment", + "tableTo": "volume_backup", + "columnsFrom": [ + "volumeBackupId" + ], + "columnsTo": [ + "volumeBackupId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.destination": { + "name": "destination", + "schema": "", + "columns": { + "destinationId": { + "name": "destinationId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "accessKey": { + "name": "accessKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secretAccessKey": { + "name": "secretAccessKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bucket": { + "name": "bucket", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "region": { + "name": "region", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "endpoint": { + "name": "endpoint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "destination_organizationId_organization_id_fk": { + "name": "destination_organizationId_organization_id_fk", + "tableFrom": "destination", + "tableTo": "organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.domain": { + "name": "domain", + "schema": "", + "columns": { + "domainId": { + "name": "domainId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "host": { + "name": "host", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "https": { + "name": "https", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "port": { + "name": "port", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 3000 + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "serviceName": { + "name": "serviceName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "domainType": { + "name": "domainType", + "type": "domainType", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'application'" + }, + "uniqueConfigKey": { + "name": "uniqueConfigKey", + "type": "serial", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customCertResolver": { + "name": "customCertResolver", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previewDeploymentId": { + "name": "previewDeploymentId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "certificateType": { + "name": "certificateType", + "type": "certificateType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "internalPath": { + "name": "internalPath", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "stripPath": { + "name": "stripPath", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "domain_composeId_compose_composeId_fk": { + "name": "domain_composeId_compose_composeId_fk", + "tableFrom": "domain", + "tableTo": "compose", + "columnsFrom": [ + "composeId" + ], + "columnsTo": [ + "composeId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "domain_applicationId_application_applicationId_fk": { + "name": "domain_applicationId_application_applicationId_fk", + "tableFrom": "domain", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "domain_previewDeploymentId_preview_deployments_previewDeploymentId_fk": { + "name": "domain_previewDeploymentId_preview_deployments_previewDeploymentId_fk", + "tableFrom": "domain", + "tableTo": "preview_deployments", + "columnsFrom": [ + "previewDeploymentId" + ], + "columnsTo": [ + "previewDeploymentId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.environment": { + "name": "environment", + "schema": "", + "columns": { + "environmentId": { + "name": "environmentId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "environment_projectId_project_projectId_fk": { + "name": "environment_projectId_project_projectId_fk", + "tableFrom": "environment", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.git_provider": { + "name": "git_provider", + "schema": "", + "columns": { + "gitProviderId": { + "name": "gitProviderId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "providerType": { + "name": "providerType", + "type": "gitProviderType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'github'" + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "git_provider_organizationId_organization_id_fk": { + "name": "git_provider_organizationId_organization_id_fk", + "tableFrom": "git_provider", + "tableTo": "organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "git_provider_userId_user_temp_id_fk": { + "name": "git_provider_userId_user_temp_id_fk", + "tableFrom": "git_provider", + "tableTo": "user_temp", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.gitea": { + "name": "gitea", + "schema": "", + "columns": { + "giteaId": { + "name": "giteaId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "giteaUrl": { + "name": "giteaUrl", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'https://gitea.com'" + }, + "redirect_uri": { + "name": "redirect_uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_secret": { + "name": "client_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitProviderId": { + "name": "gitProviderId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'repo,repo:status,read:user,read:org'" + }, + "last_authenticated_at": { + "name": "last_authenticated_at", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "gitea_gitProviderId_git_provider_gitProviderId_fk": { + "name": "gitea_gitProviderId_git_provider_gitProviderId_fk", + "tableFrom": "gitea", + "tableTo": "git_provider", + "columnsFrom": [ + "gitProviderId" + ], + "columnsTo": [ + "gitProviderId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.github": { + "name": "github", + "schema": "", + "columns": { + "githubId": { + "name": "githubId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "githubAppName": { + "name": "githubAppName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "githubAppId": { + "name": "githubAppId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "githubClientId": { + "name": "githubClientId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "githubClientSecret": { + "name": "githubClientSecret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "githubInstallationId": { + "name": "githubInstallationId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "githubPrivateKey": { + "name": "githubPrivateKey", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "githubWebhookSecret": { + "name": "githubWebhookSecret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitProviderId": { + "name": "gitProviderId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "github_gitProviderId_git_provider_gitProviderId_fk": { + "name": "github_gitProviderId_git_provider_gitProviderId_fk", + "tableFrom": "github", + "tableTo": "git_provider", + "columnsFrom": [ + "gitProviderId" + ], + "columnsTo": [ + "gitProviderId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.gitlab": { + "name": "gitlab", + "schema": "", + "columns": { + "gitlabId": { + "name": "gitlabId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "gitlabUrl": { + "name": "gitlabUrl", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'https://gitlab.com'" + }, + "application_id": { + "name": "application_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redirect_uri": { + "name": "redirect_uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "group_name": { + "name": "group_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "gitProviderId": { + "name": "gitProviderId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "gitlab_gitProviderId_git_provider_gitProviderId_fk": { + "name": "gitlab_gitProviderId_git_provider_gitProviderId_fk", + "tableFrom": "gitlab", + "tableTo": "git_provider", + "columnsFrom": [ + "gitProviderId" + ], + "columnsTo": [ + "gitProviderId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mariadb": { + "name": "mariadb", + "schema": "", + "columns": { + "mariadbId": { + "name": "mariadbId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "databaseName": { + "name": "databaseName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databaseUser": { + "name": "databaseUser", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databasePassword": { + "name": "databasePassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rootPassword": { + "name": "rootPassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "externalPort": { + "name": "externalPort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "healthCheckSwarm": { + "name": "healthCheckSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "restartPolicySwarm": { + "name": "restartPolicySwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "placementSwarm": { + "name": "placementSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "updateConfigSwarm": { + "name": "updateConfigSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "rollbackConfigSwarm": { + "name": "rollbackConfigSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "modeSwarm": { + "name": "modeSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "labelsSwarm": { + "name": "labelsSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "networkSwarm": { + "name": "networkSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "replicas": { + "name": "replicas", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "environmentId": { + "name": "environmentId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "mariadb_environmentId_environment_environmentId_fk": { + "name": "mariadb_environmentId_environment_environmentId_fk", + "tableFrom": "mariadb", + "tableTo": "environment", + "columnsFrom": [ + "environmentId" + ], + "columnsTo": [ + "environmentId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mariadb_serverId_server_serverId_fk": { + "name": "mariadb_serverId_server_serverId_fk", + "tableFrom": "mariadb", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mariadb_appName_unique": { + "name": "mariadb_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mongo": { + "name": "mongo", + "schema": "", + "columns": { + "mongoId": { + "name": "mongoId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "databaseUser": { + "name": "databaseUser", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databasePassword": { + "name": "databasePassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "externalPort": { + "name": "externalPort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "healthCheckSwarm": { + "name": "healthCheckSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "restartPolicySwarm": { + "name": "restartPolicySwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "placementSwarm": { + "name": "placementSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "updateConfigSwarm": { + "name": "updateConfigSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "rollbackConfigSwarm": { + "name": "rollbackConfigSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "modeSwarm": { + "name": "modeSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "labelsSwarm": { + "name": "labelsSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "networkSwarm": { + "name": "networkSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "replicas": { + "name": "replicas", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "environmentId": { + "name": "environmentId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "replicaSets": { + "name": "replicaSets", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "mongo_environmentId_environment_environmentId_fk": { + "name": "mongo_environmentId_environment_environmentId_fk", + "tableFrom": "mongo", + "tableTo": "environment", + "columnsFrom": [ + "environmentId" + ], + "columnsTo": [ + "environmentId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mongo_serverId_server_serverId_fk": { + "name": "mongo_serverId_server_serverId_fk", + "tableFrom": "mongo", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mongo_appName_unique": { + "name": "mongo_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mount": { + "name": "mount", + "schema": "", + "columns": { + "mountId": { + "name": "mountId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "type": { + "name": "type", + "type": "mountType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "hostPath": { + "name": "hostPath", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "volumeName": { + "name": "volumeName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "filePath": { + "name": "filePath", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serviceType": { + "name": "serviceType", + "type": "serviceType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'application'" + }, + "mountPath": { + "name": "mountPath", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "postgresId": { + "name": "postgresId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mariadbId": { + "name": "mariadbId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mongoId": { + "name": "mongoId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mysqlId": { + "name": "mysqlId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redisId": { + "name": "redisId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "mount_applicationId_application_applicationId_fk": { + "name": "mount_applicationId_application_applicationId_fk", + "tableFrom": "mount", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_postgresId_postgres_postgresId_fk": { + "name": "mount_postgresId_postgres_postgresId_fk", + "tableFrom": "mount", + "tableTo": "postgres", + "columnsFrom": [ + "postgresId" + ], + "columnsTo": [ + "postgresId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_mariadbId_mariadb_mariadbId_fk": { + "name": "mount_mariadbId_mariadb_mariadbId_fk", + "tableFrom": "mount", + "tableTo": "mariadb", + "columnsFrom": [ + "mariadbId" + ], + "columnsTo": [ + "mariadbId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_mongoId_mongo_mongoId_fk": { + "name": "mount_mongoId_mongo_mongoId_fk", + "tableFrom": "mount", + "tableTo": "mongo", + "columnsFrom": [ + "mongoId" + ], + "columnsTo": [ + "mongoId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_mysqlId_mysql_mysqlId_fk": { + "name": "mount_mysqlId_mysql_mysqlId_fk", + "tableFrom": "mount", + "tableTo": "mysql", + "columnsFrom": [ + "mysqlId" + ], + "columnsTo": [ + "mysqlId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_redisId_redis_redisId_fk": { + "name": "mount_redisId_redis_redisId_fk", + "tableFrom": "mount", + "tableTo": "redis", + "columnsFrom": [ + "redisId" + ], + "columnsTo": [ + "redisId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_composeId_compose_composeId_fk": { + "name": "mount_composeId_compose_composeId_fk", + "tableFrom": "mount", + "tableTo": "compose", + "columnsFrom": [ + "composeId" + ], + "columnsTo": [ + "composeId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mysql": { + "name": "mysql", + "schema": "", + "columns": { + "mysqlId": { + "name": "mysqlId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "databaseName": { + "name": "databaseName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databaseUser": { + "name": "databaseUser", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databasePassword": { + "name": "databasePassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rootPassword": { + "name": "rootPassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "externalPort": { + "name": "externalPort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "healthCheckSwarm": { + "name": "healthCheckSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "restartPolicySwarm": { + "name": "restartPolicySwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "placementSwarm": { + "name": "placementSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "updateConfigSwarm": { + "name": "updateConfigSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "rollbackConfigSwarm": { + "name": "rollbackConfigSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "modeSwarm": { + "name": "modeSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "labelsSwarm": { + "name": "labelsSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "networkSwarm": { + "name": "networkSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "replicas": { + "name": "replicas", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "environmentId": { + "name": "environmentId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "mysql_environmentId_environment_environmentId_fk": { + "name": "mysql_environmentId_environment_environmentId_fk", + "tableFrom": "mysql", + "tableTo": "environment", + "columnsFrom": [ + "environmentId" + ], + "columnsTo": [ + "environmentId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mysql_serverId_server_serverId_fk": { + "name": "mysql_serverId_server_serverId_fk", + "tableFrom": "mysql", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mysql_appName_unique": { + "name": "mysql_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.discord": { + "name": "discord", + "schema": "", + "columns": { + "discordId": { + "name": "discordId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "webhookUrl": { + "name": "webhookUrl", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "decoration": { + "name": "decoration", + "type": "boolean", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.email": { + "name": "email", + "schema": "", + "columns": { + "emailId": { + "name": "emailId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "smtpServer": { + "name": "smtpServer", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "smtpPort": { + "name": "smtpPort", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "fromAddress": { + "name": "fromAddress", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "toAddress": { + "name": "toAddress", + "type": "text[]", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.gotify": { + "name": "gotify", + "schema": "", + "columns": { + "gotifyId": { + "name": "gotifyId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "serverUrl": { + "name": "serverUrl", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appToken": { + "name": "appToken", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 5 + }, + "decoration": { + "name": "decoration", + "type": "boolean", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.notification": { + "name": "notification", + "schema": "", + "columns": { + "notificationId": { + "name": "notificationId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appDeploy": { + "name": "appDeploy", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "appBuildError": { + "name": "appBuildError", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "databaseBackup": { + "name": "databaseBackup", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "dokployRestart": { + "name": "dokployRestart", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "dockerCleanup": { + "name": "dockerCleanup", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "serverThreshold": { + "name": "serverThreshold", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "notificationType": { + "name": "notificationType", + "type": "notificationType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slackId": { + "name": "slackId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "telegramId": { + "name": "telegramId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "discordId": { + "name": "discordId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "emailId": { + "name": "emailId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gotifyId": { + "name": "gotifyId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ntfyId": { + "name": "ntfyId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "notification_slackId_slack_slackId_fk": { + "name": "notification_slackId_slack_slackId_fk", + "tableFrom": "notification", + "tableTo": "slack", + "columnsFrom": [ + "slackId" + ], + "columnsTo": [ + "slackId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_telegramId_telegram_telegramId_fk": { + "name": "notification_telegramId_telegram_telegramId_fk", + "tableFrom": "notification", + "tableTo": "telegram", + "columnsFrom": [ + "telegramId" + ], + "columnsTo": [ + "telegramId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_discordId_discord_discordId_fk": { + "name": "notification_discordId_discord_discordId_fk", + "tableFrom": "notification", + "tableTo": "discord", + "columnsFrom": [ + "discordId" + ], + "columnsTo": [ + "discordId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_emailId_email_emailId_fk": { + "name": "notification_emailId_email_emailId_fk", + "tableFrom": "notification", + "tableTo": "email", + "columnsFrom": [ + "emailId" + ], + "columnsTo": [ + "emailId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_gotifyId_gotify_gotifyId_fk": { + "name": "notification_gotifyId_gotify_gotifyId_fk", + "tableFrom": "notification", + "tableTo": "gotify", + "columnsFrom": [ + "gotifyId" + ], + "columnsTo": [ + "gotifyId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_ntfyId_ntfy_ntfyId_fk": { + "name": "notification_ntfyId_ntfy_ntfyId_fk", + "tableFrom": "notification", + "tableTo": "ntfy", + "columnsFrom": [ + "ntfyId" + ], + "columnsTo": [ + "ntfyId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_organizationId_organization_id_fk": { + "name": "notification_organizationId_organization_id_fk", + "tableFrom": "notification", + "tableTo": "organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ntfy": { + "name": "ntfy", + "schema": "", + "columns": { + "ntfyId": { + "name": "ntfyId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "serverUrl": { + "name": "serverUrl", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "topic": { + "name": "topic", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "accessToken": { + "name": "accessToken", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 3 + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.slack": { + "name": "slack", + "schema": "", + "columns": { + "slackId": { + "name": "slackId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "webhookUrl": { + "name": "webhookUrl", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "channel": { + "name": "channel", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.telegram": { + "name": "telegram", + "schema": "", + "columns": { + "telegramId": { + "name": "telegramId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "botToken": { + "name": "botToken", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chatId": { + "name": "chatId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "messageThreadId": { + "name": "messageThreadId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.port": { + "name": "port", + "schema": "", + "columns": { + "portId": { + "name": "portId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "publishedPort": { + "name": "publishedPort", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "publishMode": { + "name": "publishMode", + "type": "publishModeType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'host'" + }, + "targetPort": { + "name": "targetPort", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "protocol": { + "name": "protocol", + "type": "protocolType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "port_applicationId_application_applicationId_fk": { + "name": "port_applicationId_application_applicationId_fk", + "tableFrom": "port", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.postgres": { + "name": "postgres", + "schema": "", + "columns": { + "postgresId": { + "name": "postgresId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databaseName": { + "name": "databaseName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databaseUser": { + "name": "databaseUser", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databasePassword": { + "name": "databasePassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "externalPort": { + "name": "externalPort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "healthCheckSwarm": { + "name": "healthCheckSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "restartPolicySwarm": { + "name": "restartPolicySwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "placementSwarm": { + "name": "placementSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "updateConfigSwarm": { + "name": "updateConfigSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "rollbackConfigSwarm": { + "name": "rollbackConfigSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "modeSwarm": { + "name": "modeSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "labelsSwarm": { + "name": "labelsSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "networkSwarm": { + "name": "networkSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "replicas": { + "name": "replicas", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "environmentId": { + "name": "environmentId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "postgres_environmentId_environment_environmentId_fk": { + "name": "postgres_environmentId_environment_environmentId_fk", + "tableFrom": "postgres", + "tableTo": "environment", + "columnsFrom": [ + "environmentId" + ], + "columnsTo": [ + "environmentId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "postgres_serverId_server_serverId_fk": { + "name": "postgres_serverId_server_serverId_fk", + "tableFrom": "postgres", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "postgres_appName_unique": { + "name": "postgres_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.preview_deployments": { + "name": "preview_deployments", + "schema": "", + "columns": { + "previewDeploymentId": { + "name": "previewDeploymentId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pullRequestId": { + "name": "pullRequestId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pullRequestNumber": { + "name": "pullRequestNumber", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pullRequestURL": { + "name": "pullRequestURL", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pullRequestTitle": { + "name": "pullRequestTitle", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pullRequestCommentId": { + "name": "pullRequestCommentId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "previewStatus": { + "name": "previewStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "domainId": { + "name": "domainId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expiresAt": { + "name": "expiresAt", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "preview_deployments_applicationId_application_applicationId_fk": { + "name": "preview_deployments_applicationId_application_applicationId_fk", + "tableFrom": "preview_deployments", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "preview_deployments_domainId_domain_domainId_fk": { + "name": "preview_deployments_domainId_domain_domainId_fk", + "tableFrom": "preview_deployments", + "tableTo": "domain", + "columnsFrom": [ + "domainId" + ], + "columnsTo": [ + "domainId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "preview_deployments_appName_unique": { + "name": "preview_deployments_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project": { + "name": "project", + "schema": "", + "columns": { + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + } + }, + "indexes": {}, + "foreignKeys": { + "project_organizationId_organization_id_fk": { + "name": "project_organizationId_organization_id_fk", + "tableFrom": "project", + "tableTo": "organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.redirect": { + "name": "redirect", + "schema": "", + "columns": { + "redirectId": { + "name": "redirectId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "regex": { + "name": "regex", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "replacement": { + "name": "replacement", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permanent": { + "name": "permanent", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "uniqueConfigKey": { + "name": "uniqueConfigKey", + "type": "serial", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "redirect_applicationId_application_applicationId_fk": { + "name": "redirect_applicationId_application_applicationId_fk", + "tableFrom": "redirect", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.redis": { + "name": "redis", + "schema": "", + "columns": { + "redisId": { + "name": "redisId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "externalPort": { + "name": "externalPort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "healthCheckSwarm": { + "name": "healthCheckSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "restartPolicySwarm": { + "name": "restartPolicySwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "placementSwarm": { + "name": "placementSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "updateConfigSwarm": { + "name": "updateConfigSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "rollbackConfigSwarm": { + "name": "rollbackConfigSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "modeSwarm": { + "name": "modeSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "labelsSwarm": { + "name": "labelsSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "networkSwarm": { + "name": "networkSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "replicas": { + "name": "replicas", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "environmentId": { + "name": "environmentId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "redis_environmentId_environment_environmentId_fk": { + "name": "redis_environmentId_environment_environmentId_fk", + "tableFrom": "redis", + "tableTo": "environment", + "columnsFrom": [ + "environmentId" + ], + "columnsTo": [ + "environmentId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "redis_serverId_server_serverId_fk": { + "name": "redis_serverId_server_serverId_fk", + "tableFrom": "redis", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "redis_appName_unique": { + "name": "redis_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.registry": { + "name": "registry", + "schema": "", + "columns": { + "registryId": { + "name": "registryId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "registryName": { + "name": "registryName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "imagePrefix": { + "name": "imagePrefix", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "registryUrl": { + "name": "registryUrl", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "selfHosted": { + "name": "selfHosted", + "type": "RegistryType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'cloud'" + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "registry_organizationId_organization_id_fk": { + "name": "registry_organizationId_organization_id_fk", + "tableFrom": "registry", + "tableTo": "organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.rollback": { + "name": "rollback", + "schema": "", + "columns": { + "rollbackId": { + "name": "rollbackId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "deploymentId": { + "name": "deploymentId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "serial", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "fullContext": { + "name": "fullContext", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "rollback_deploymentId_deployment_deploymentId_fk": { + "name": "rollback_deploymentId_deployment_deploymentId_fk", + "tableFrom": "rollback", + "tableTo": "deployment", + "columnsFrom": [ + "deploymentId" + ], + "columnsTo": [ + "deploymentId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.schedule": { + "name": "schedule", + "schema": "", + "columns": { + "scheduleId": { + "name": "scheduleId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cronExpression": { + "name": "cronExpression", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serviceName": { + "name": "serviceName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shellType": { + "name": "shellType", + "type": "shellType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'bash'" + }, + "scheduleType": { + "name": "scheduleType", + "type": "scheduleType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'application'" + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "script": { + "name": "script", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "schedule_applicationId_application_applicationId_fk": { + "name": "schedule_applicationId_application_applicationId_fk", + "tableFrom": "schedule", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "schedule_composeId_compose_composeId_fk": { + "name": "schedule_composeId_compose_composeId_fk", + "tableFrom": "schedule", + "tableTo": "compose", + "columnsFrom": [ + "composeId" + ], + "columnsTo": [ + "composeId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "schedule_serverId_server_serverId_fk": { + "name": "schedule_serverId_server_serverId_fk", + "tableFrom": "schedule", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "schedule_userId_user_temp_id_fk": { + "name": "schedule_userId_user_temp_id_fk", + "tableFrom": "schedule", + "tableTo": "user_temp", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.security": { + "name": "security", + "schema": "", + "columns": { + "securityId": { + "name": "securityId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "security_applicationId_application_applicationId_fk": { + "name": "security_applicationId_application_applicationId_fk", + "tableFrom": "security", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "security_username_applicationId_unique": { + "name": "security_username_applicationId_unique", + "nullsNotDistinct": false, + "columns": [ + "username", + "applicationId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.server": { + "name": "server", + "schema": "", + "columns": { + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ipAddress": { + "name": "ipAddress", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "port": { + "name": "port", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'root'" + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enableDockerCleanup": { + "name": "enableDockerCleanup", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverStatus": { + "name": "serverStatus", + "type": "serverStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "sshKeyId": { + "name": "sshKeyId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metricsConfig": { + "name": "metricsConfig", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"server\":{\"type\":\"Remote\",\"refreshRate\":60,\"port\":4500,\"token\":\"\",\"urlCallback\":\"\",\"cronJob\":\"\",\"retentionDays\":2,\"thresholds\":{\"cpu\":0,\"memory\":0}},\"containers\":{\"refreshRate\":60,\"services\":{\"include\":[],\"exclude\":[]}}}'::jsonb" + } + }, + "indexes": {}, + "foreignKeys": { + "server_organizationId_organization_id_fk": { + "name": "server_organizationId_organization_id_fk", + "tableFrom": "server", + "tableTo": "organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "server_sshKeyId_ssh-key_sshKeyId_fk": { + "name": "server_sshKeyId_ssh-key_sshKeyId_fk", + "tableFrom": "server", + "tableTo": "ssh-key", + "columnsFrom": [ + "sshKeyId" + ], + "columnsTo": [ + "sshKeyId" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session_temp": { + "name": "session_temp", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "impersonated_by": { + "name": "impersonated_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "active_organization_id": { + "name": "active_organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "session_temp_user_id_user_temp_id_fk": { + "name": "session_temp_user_id_user_temp_id_fk", + "tableFrom": "session_temp", + "tableTo": "user_temp", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_temp_token_unique": { + "name": "session_temp_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ssh-key": { + "name": "ssh-key", + "schema": "", + "columns": { + "sshKeyId": { + "name": "sshKeyId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "privateKey": { + "name": "privateKey", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "publicKey": { + "name": "publicKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lastUsedAt": { + "name": "lastUsedAt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "ssh-key_organizationId_organization_id_fk": { + "name": "ssh-key_organizationId_organization_id_fk", + "tableFrom": "ssh-key", + "tableTo": "organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_temp": { + "name": "user_temp", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "isRegistered": { + "name": "isRegistered", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "expirationDate": { + "name": "expirationDate", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "two_factor_enabled": { + "name": "two_factor_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "serverIp": { + "name": "serverIp", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "certificateType": { + "name": "certificateType", + "type": "certificateType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "https": { + "name": "https", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "host": { + "name": "host", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "letsEncryptEmail": { + "name": "letsEncryptEmail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sshPrivateKey": { + "name": "sshPrivateKey", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enableDockerCleanup": { + "name": "enableDockerCleanup", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "logCleanupCron": { + "name": "logCleanupCron", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'0 0 * * *'" + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'user'" + }, + "enablePaidFeatures": { + "name": "enablePaidFeatures", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "allowImpersonation": { + "name": "allowImpersonation", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "metricsConfig": { + "name": "metricsConfig", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"server\":{\"type\":\"Dokploy\",\"refreshRate\":60,\"port\":4500,\"token\":\"\",\"retentionDays\":2,\"cronJob\":\"\",\"urlCallback\":\"\",\"thresholds\":{\"cpu\":0,\"memory\":0}},\"containers\":{\"refreshRate\":60,\"services\":{\"include\":[],\"exclude\":[]}}}'::jsonb" + }, + "cleanupCacheApplications": { + "name": "cleanupCacheApplications", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cleanupCacheOnPreviews": { + "name": "cleanupCacheOnPreviews", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cleanupCacheOnCompose": { + "name": "cleanupCacheOnCompose", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "stripeCustomerId": { + "name": "stripeCustomerId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripeSubscriptionId": { + "name": "stripeSubscriptionId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serversQuantity": { + "name": "serversQuantity", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_temp_email_unique": { + "name": "user_temp_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.volume_backup": { + "name": "volume_backup", + "schema": "", + "columns": { + "volumeBackupId": { + "name": "volumeBackupId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "volumeName": { + "name": "volumeName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "prefix": { + "name": "prefix", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serviceType": { + "name": "serviceType", + "type": "serviceType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'application'" + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serviceName": { + "name": "serviceName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "turnOff": { + "name": "turnOff", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cronExpression": { + "name": "cronExpression", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "keepLatestCount": { + "name": "keepLatestCount", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "postgresId": { + "name": "postgresId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mariadbId": { + "name": "mariadbId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mongoId": { + "name": "mongoId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mysqlId": { + "name": "mysqlId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redisId": { + "name": "redisId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "destinationId": { + "name": "destinationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "volume_backup_applicationId_application_applicationId_fk": { + "name": "volume_backup_applicationId_application_applicationId_fk", + "tableFrom": "volume_backup", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "volume_backup_postgresId_postgres_postgresId_fk": { + "name": "volume_backup_postgresId_postgres_postgresId_fk", + "tableFrom": "volume_backup", + "tableTo": "postgres", + "columnsFrom": [ + "postgresId" + ], + "columnsTo": [ + "postgresId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "volume_backup_mariadbId_mariadb_mariadbId_fk": { + "name": "volume_backup_mariadbId_mariadb_mariadbId_fk", + "tableFrom": "volume_backup", + "tableTo": "mariadb", + "columnsFrom": [ + "mariadbId" + ], + "columnsTo": [ + "mariadbId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "volume_backup_mongoId_mongo_mongoId_fk": { + "name": "volume_backup_mongoId_mongo_mongoId_fk", + "tableFrom": "volume_backup", + "tableTo": "mongo", + "columnsFrom": [ + "mongoId" + ], + "columnsTo": [ + "mongoId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "volume_backup_mysqlId_mysql_mysqlId_fk": { + "name": "volume_backup_mysqlId_mysql_mysqlId_fk", + "tableFrom": "volume_backup", + "tableTo": "mysql", + "columnsFrom": [ + "mysqlId" + ], + "columnsTo": [ + "mysqlId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "volume_backup_redisId_redis_redisId_fk": { + "name": "volume_backup_redisId_redis_redisId_fk", + "tableFrom": "volume_backup", + "tableTo": "redis", + "columnsFrom": [ + "redisId" + ], + "columnsTo": [ + "redisId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "volume_backup_composeId_compose_composeId_fk": { + "name": "volume_backup_composeId_compose_composeId_fk", + "tableFrom": "volume_backup", + "tableTo": "compose", + "columnsFrom": [ + "composeId" + ], + "columnsTo": [ + "composeId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "volume_backup_destinationId_destination_destinationId_fk": { + "name": "volume_backup_destinationId_destination_destinationId_fk", + "tableFrom": "volume_backup", + "tableTo": "destination", + "columnsFrom": [ + "destinationId" + ], + "columnsTo": [ + "destinationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.buildType": { + "name": "buildType", + "schema": "public", + "values": [ + "dockerfile", + "heroku_buildpacks", + "paketo_buildpacks", + "nixpacks", + "static", + "railpack" + ] + }, + "public.sourceType": { + "name": "sourceType", + "schema": "public", + "values": [ + "docker", + "git", + "github", + "gitlab", + "bitbucket", + "gitea", + "drop" + ] + }, + "public.backupType": { + "name": "backupType", + "schema": "public", + "values": [ + "database", + "compose" + ] + }, + "public.databaseType": { + "name": "databaseType", + "schema": "public", + "values": [ + "postgres", + "mariadb", + "mysql", + "mongo", + "web-server" + ] + }, + "public.composeType": { + "name": "composeType", + "schema": "public", + "values": [ + "docker-compose", + "stack" + ] + }, + "public.sourceTypeCompose": { + "name": "sourceTypeCompose", + "schema": "public", + "values": [ + "git", + "github", + "gitlab", + "bitbucket", + "gitea", + "raw" + ] + }, + "public.deploymentStatus": { + "name": "deploymentStatus", + "schema": "public", + "values": [ + "running", + "done", + "error" + ] + }, + "public.domainType": { + "name": "domainType", + "schema": "public", + "values": [ + "compose", + "application", + "preview" + ] + }, + "public.gitProviderType": { + "name": "gitProviderType", + "schema": "public", + "values": [ + "github", + "gitlab", + "bitbucket", + "gitea" + ] + }, + "public.mountType": { + "name": "mountType", + "schema": "public", + "values": [ + "bind", + "volume", + "file" + ] + }, + "public.serviceType": { + "name": "serviceType", + "schema": "public", + "values": [ + "application", + "postgres", + "mysql", + "mariadb", + "mongo", + "redis", + "compose" + ] + }, + "public.notificationType": { + "name": "notificationType", + "schema": "public", + "values": [ + "slack", + "telegram", + "discord", + "email", + "gotify", + "ntfy" + ] + }, + "public.protocolType": { + "name": "protocolType", + "schema": "public", + "values": [ + "tcp", + "udp" + ] + }, + "public.publishModeType": { + "name": "publishModeType", + "schema": "public", + "values": [ + "ingress", + "host" + ] + }, + "public.RegistryType": { + "name": "RegistryType", + "schema": "public", + "values": [ + "selfHosted", + "cloud" + ] + }, + "public.scheduleType": { + "name": "scheduleType", + "schema": "public", + "values": [ + "application", + "compose", + "server", + "dokploy-server" + ] + }, + "public.shellType": { + "name": "shellType", + "schema": "public", + "values": [ + "bash", + "sh" + ] + }, + "public.serverStatus": { + "name": "serverStatus", + "schema": "public", + "values": [ + "active", + "inactive" + ] + }, + "public.applicationStatus": { + "name": "applicationStatus", + "schema": "public", + "values": [ + "idle", + "running", + "done", + "error" + ] + }, + "public.certificateType": { + "name": "certificateType", + "schema": "public", + "values": [ + "letsencrypt", + "none", + "custom" + ] + }, + "public.triggerType": { + "name": "triggerType", + "schema": "public", + "values": [ + "push", + "tag" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/apps/dokploy/drizzle/meta/_journal.json b/apps/dokploy/drizzle/meta/_journal.json index 6c2d58c31..a7862dbed 100644 --- a/apps/dokploy/drizzle/meta/_journal.json +++ b/apps/dokploy/drizzle/meta/_journal.json @@ -771,6 +771,13 @@ "when": 1757052053574, "tag": "0109_remarkable_sauron", "breakpoints": true + }, + { + "idx": 110, + "version": "7", + "when": 1757189541734, + "tag": "0110_red_psynapse", + "breakpoints": true } ] } \ No newline at end of file diff --git a/apps/dokploy/package.json b/apps/dokploy/package.json index 66d9f7edd..463db904e 100644 --- a/apps/dokploy/package.json +++ b/apps/dokploy/package.json @@ -1,6 +1,6 @@ { "name": "dokploy", - "version": "v0.25.0", + "version": "v0.25.3", "private": true, "license": "Apache-2.0", "type": "module", @@ -112,7 +112,7 @@ "i18next": "^23.16.8", "input-otp": "^1.4.2", "js-cookie": "^3.0.5", - "js-yaml": "4.1.0", + "yaml": "2.8.1", "lodash": "4.17.21", "lucide-react": "^0.469.0", "micromatch": "4.0.8", @@ -160,7 +160,6 @@ "@types/adm-zip": "^0.5.7", "@types/bcrypt": "5.0.2", "@types/js-cookie": "^3.0.6", - "@types/js-yaml": "4.0.9", "@types/lodash": "4.17.4", "@types/micromatch": "4.0.9", "@types/node": "^18.19.104", diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId].tsx index f87ee8687..100b6f2e3 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId].tsx @@ -80,7 +80,6 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; -import { Input } from "@/components/ui/input"; import { Popover, PopoverContent, @@ -96,6 +95,7 @@ import { import { cn } from "@/lib/utils"; import { appRouter } from "@/server/api/root"; import { api } from "@/utils/api"; +import { FocusShortcutInput } from "@/components/shared/focus-shortcut-input"; export type Services = { appName: string; @@ -1197,7 +1197,7 @@ const EnvironmentPage = (
- setSearchQuery(e.target.value)} diff --git a/apps/dokploy/server/api/routers/application.ts b/apps/dokploy/server/api/routers/application.ts index b89637de3..45fe15c6f 100644 --- a/apps/dokploy/server/api/routers/application.ts +++ b/apps/dokploy/server/api/routers/application.ts @@ -24,6 +24,7 @@ import { unzipDrop, updateApplication, updateApplicationStatus, + updateDeploymentStatus, writeConfig, writeConfigRemote, // uploadFileSchema @@ -40,8 +41,10 @@ import { import { db } from "@/server/db"; import { apiCreateApplication, + apiDeployApplication, apiFindMonitoringStats, apiFindOneApplication, + apiRedeployApplication, apiReloadApplication, apiSaveBitbucketProvider, apiSaveBuildType, @@ -56,7 +59,7 @@ import { } from "@/server/db/schema"; import type { DeploymentJob } from "@/server/queues/queue-types"; import { cleanQueuesByApplication, myQueue } from "@/server/queues/queueSetup"; -import { deploy } from "@/server/utils/deploy"; +import { cancelDeployment, deploy } from "@/server/utils/deploy"; import { uploadFileSchema } from "@/utils/schema"; export const applicationRouter = createTRPCRouter({ @@ -306,7 +309,7 @@ export const applicationRouter = createTRPCRouter({ }), redeploy: protectedProcedure - .input(apiFindOneApplication) + .input(apiRedeployApplication) .mutation(async ({ input, ctx }) => { const application = await findApplicationById(input.applicationId); if ( @@ -320,8 +323,8 @@ export const applicationRouter = createTRPCRouter({ } const jobData: DeploymentJob = { applicationId: input.applicationId, - titleLog: "Rebuild deployment", - descriptionLog: "", + titleLog: input.title || "Rebuild deployment", + descriptionLog: input.description || "", type: "redeploy", applicationType: "application", server: !!application.serverId, @@ -670,7 +673,7 @@ export const applicationRouter = createTRPCRouter({ return true; }), deploy: protectedProcedure - .input(apiFindOneApplication) + .input(apiDeployApplication) .mutation(async ({ input, ctx }) => { const application = await findApplicationById(input.applicationId); if ( @@ -684,8 +687,8 @@ export const applicationRouter = createTRPCRouter({ } const jobData: DeploymentJob = { applicationId: input.applicationId, - titleLog: "Manual deployment", - descriptionLog: "", + titleLog: input.title || "Manual deployment", + descriptionLog: input.description || "", type: "deploy", applicationType: "application", server: !!application.serverId, @@ -894,4 +897,55 @@ export const applicationRouter = createTRPCRouter({ return updatedApplication; }), + + cancelDeployment: protectedProcedure + .input(apiFindOneApplication) + .mutation(async ({ input, ctx }) => { + const application = await findApplicationById(input.applicationId); + if ( + application.environment.project.organizationId !== + ctx.session.activeOrganizationId + ) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to cancel this deployment", + }); + } + + if (IS_CLOUD && application.serverId) { + try { + await updateApplicationStatus(input.applicationId, "idle"); + + if (application.deployments[0]) { + await updateDeploymentStatus( + application.deployments[0].deploymentId, + "done", + ); + } + + await cancelDeployment({ + applicationId: input.applicationId, + applicationType: "application", + }); + + return { + success: true, + message: "Deployment cancellation requested", + }; + } catch (error) { + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: + error instanceof Error + ? error.message + : "Failed to cancel deployment", + }); + } + } + + throw new TRPCError({ + code: "BAD_REQUEST", + message: "Deployment cancellation only available in cloud version", + }); + }), }); diff --git a/apps/dokploy/server/api/routers/cluster.ts b/apps/dokploy/server/api/routers/cluster.ts index 7dde96df5..6c118d802 100644 --- a/apps/dokploy/server/api/routers/cluster.ts +++ b/apps/dokploy/server/api/routers/cluster.ts @@ -7,7 +7,7 @@ import { } from "@dokploy/server"; import { TRPCError } from "@trpc/server"; import { z } from "zod"; -import { getPublicIpWithFallback } from "@/server/wss/terminal"; +import { getLocalServerIp } from "@/server/wss/terminal"; import { createTRPCRouter, protectedProcedure } from "../trpc"; export const clusterRouter = createTRPCRouter({ getNodes: protectedProcedure @@ -61,7 +61,7 @@ export const clusterRouter = createTRPCRouter({ const result = await docker.swarmInspect(); const docker_version = await docker.version(); - let ip = await getPublicIpWithFallback(); + let ip = await getLocalServerIp(); if (input.serverId) { const server = await findServerById(input.serverId); ip = server?.ipAddress; @@ -85,7 +85,7 @@ export const clusterRouter = createTRPCRouter({ const result = await docker.swarmInspect(); const docker_version = await docker.version(); - let ip = await getPublicIpWithFallback(); + let ip = await getLocalServerIp(); if (input.serverId) { const server = await findServerById(input.serverId); ip = server?.ipAddress; diff --git a/apps/dokploy/server/api/routers/compose.ts b/apps/dokploy/server/api/routers/compose.ts index f84da9bc6..512ea9718 100644 --- a/apps/dokploy/server/api/routers/compose.ts +++ b/apps/dokploy/server/api/routers/compose.ts @@ -29,6 +29,7 @@ import { startCompose, stopCompose, updateCompose, + updateDeploymentStatus, } from "@dokploy/server"; import { type CompleteTemplate, @@ -38,25 +39,27 @@ import { import { processTemplate } from "@dokploy/server/templates/processors"; import { TRPCError } from "@trpc/server"; import { eq } from "drizzle-orm"; -import { dump } from "js-yaml"; import _ from "lodash"; import { nanoid } from "nanoid"; import { parse } from "toml"; +import { stringify } from "yaml"; import { z } from "zod"; import { slugify } from "@/lib/slug"; import { db } from "@/server/db"; import { apiCreateCompose, apiDeleteCompose, + apiDeployCompose, apiFetchServices, apiFindCompose, apiRandomizeCompose, + apiRedeployCompose, apiUpdateCompose, compose as composeTable, } from "@/server/db/schema"; import type { DeploymentJob } from "@/server/queues/queue-types"; import { cleanQueuesByCompose, myQueue } from "@/server/queues/queueSetup"; -import { deploy } from "@/server/utils/deploy"; +import { cancelDeployment, deploy } from "@/server/utils/deploy"; import { generatePassword } from "@/templates/utils"; import { createTRPCRouter, protectedProcedure, publicProcedure } from "../trpc"; @@ -361,13 +364,13 @@ export const composeRouter = createTRPCRouter({ } const domains = await findDomainsByComposeId(input.composeId); const composeFile = await addDomainToCompose(compose, domains); - return dump(composeFile, { + return stringify(composeFile, { lineWidth: 1000, }); }), deploy: protectedProcedure - .input(apiFindCompose) + .input(apiDeployCompose) .mutation(async ({ input, ctx }) => { const compose = await findComposeById(input.composeId); @@ -382,10 +385,10 @@ export const composeRouter = createTRPCRouter({ } const jobData: DeploymentJob = { composeId: input.composeId, - titleLog: "Manual deployment", + titleLog: input.title || "Manual deployment", type: "deploy", applicationType: "compose", - descriptionLog: "", + descriptionLog: input.description || "", server: !!compose.serverId, }; @@ -404,7 +407,7 @@ export const composeRouter = createTRPCRouter({ ); }), redeploy: protectedProcedure - .input(apiFindCompose) + .input(apiRedeployCompose) .mutation(async ({ input, ctx }) => { const compose = await findComposeById(input.composeId); if ( @@ -418,10 +421,10 @@ export const composeRouter = createTRPCRouter({ } const jobData: DeploymentJob = { composeId: input.composeId, - titleLog: "Rebuild deployment", + titleLog: input.title || "Rebuild deployment", type: "redeploy", applicationType: "compose", - descriptionLog: "", + descriptionLog: input.description || "", server: !!compose.serverId, }; if (IS_CLOUD && compose.serverId) { @@ -926,4 +929,57 @@ export const composeRouter = createTRPCRouter({ }); } }), + + cancelDeployment: protectedProcedure + .input(apiFindCompose) + .mutation(async ({ input, ctx }) => { + const compose = await findComposeById(input.composeId); + if ( + compose.environment.project.organizationId !== + ctx.session.activeOrganizationId + ) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to cancel this deployment", + }); + } + + if (IS_CLOUD && compose.serverId) { + try { + await updateCompose(input.composeId, { + composeStatus: "idle", + }); + + if (compose.deployments[0]) { + await updateDeploymentStatus( + compose.deployments[0].deploymentId, + "done", + ); + } + + await cancelDeployment({ + composeId: input.composeId, + applicationType: "compose", + }); + + return { + success: true, + message: "Deployment cancellation requested", + }; + } catch (error) { + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: + error instanceof Error + ? error.message + : "Failed to cancel deployment", + }); + } + } + + throw new TRPCError({ + code: "BAD_REQUEST", + message: "Deployment cancellation only available in cloud version", + }); + }), }); diff --git a/apps/dokploy/server/api/routers/github.ts b/apps/dokploy/server/api/routers/github.ts index 141c309e0..9e739c25c 100644 --- a/apps/dokploy/server/api/routers/github.ts +++ b/apps/dokploy/server/api/routers/github.ts @@ -3,6 +3,7 @@ import { getGithubBranches, getGithubRepositories, haveGithubRequirements, + updateGithub, updateGitProvider, } from "@dokploy/server"; import { TRPCError } from "@trpc/server"; @@ -134,5 +135,9 @@ export const githubRouter = createTRPCRouter({ name: input.name, organizationId: ctx.session.activeOrganizationId, }); + + await updateGithub(input.githubId, { + ...input, + }); }), }); diff --git a/apps/dokploy/server/api/routers/notification.ts b/apps/dokploy/server/api/routers/notification.ts index 59812fbb3..09a2aed2a 100644 --- a/apps/dokploy/server/api/routers/notification.ts +++ b/apps/dokploy/server/api/routers/notification.ts @@ -2,6 +2,7 @@ import { createDiscordNotification, createEmailNotification, createGotifyNotification, + createNtfyNotification, createSlackNotification, createTelegramNotification, findNotificationById, @@ -10,12 +11,14 @@ import { sendDiscordNotification, sendEmailNotification, sendGotifyNotification, + sendNtfyNotification, sendServerThresholdNotifications, sendSlackNotification, sendTelegramNotification, updateDiscordNotification, updateEmailNotification, updateGotifyNotification, + updateNtfyNotification, updateSlackNotification, updateTelegramNotification, } from "@dokploy/server"; @@ -33,17 +36,20 @@ import { apiCreateDiscord, apiCreateEmail, apiCreateGotify, + apiCreateNtfy, apiCreateSlack, apiCreateTelegram, apiFindOneNotification, apiTestDiscordConnection, apiTestEmailConnection, apiTestGotifyConnection, + apiTestNtfyConnection, apiTestSlackConnection, apiTestTelegramConnection, apiUpdateDiscord, apiUpdateEmail, apiUpdateGotify, + apiUpdateNtfy, apiUpdateSlack, apiUpdateTelegram, notifications, @@ -321,6 +327,7 @@ export const notificationRouter = createTRPCRouter({ discord: true, email: true, gotify: true, + ntfy: true, }, orderBy: desc(notifications.createdAt), where: eq(notifications.organizationId, ctx.session.activeOrganizationId), @@ -446,6 +453,64 @@ export const notificationRouter = createTRPCRouter({ }); } }), + createNtfy: adminProcedure + .input(apiCreateNtfy) + .mutation(async ({ input, ctx }) => { + try { + return await createNtfyNotification( + input, + ctx.session.activeOrganizationId, + ); + } catch (error) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: "Error creating the notification", + cause: error, + }); + } + }), + updateNtfy: adminProcedure + .input(apiUpdateNtfy) + .mutation(async ({ input, ctx }) => { + try { + const notification = await findNotificationById(input.notificationId); + if ( + IS_CLOUD && + notification.organizationId !== ctx.session.activeOrganizationId + ) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to update this notification", + }); + } + return await updateNtfyNotification({ + ...input, + organizationId: ctx.session.activeOrganizationId, + }); + } catch (error) { + throw error; + } + }), + testNtfyConnection: adminProcedure + .input(apiTestNtfyConnection) + .mutation(async ({ input }) => { + try { + await sendNtfyNotification( + input, + "Test Notification", + "", + "view, visit Dokploy on Github, https://github.com/dokploy/dokploy, clear=true;", + "Hi, From Dokploy 👋", + ); + return true; + } catch (error) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: "Error testing the notification", + cause: error, + }); + } + }), getEmailProviders: adminProcedure.query(async ({ ctx }) => { return await db.query.notifications.findMany({ where: eq(notifications.organizationId, ctx.session.activeOrganizationId), diff --git a/apps/dokploy/server/api/routers/settings.ts b/apps/dokploy/server/api/routers/settings.ts index 02678b990..b4968c260 100644 --- a/apps/dokploy/server/api/routers/settings.ts +++ b/apps/dokploy/server/api/routers/settings.ts @@ -46,8 +46,8 @@ import { import { generateOpenApiDocument } from "@dokploy/trpc-openapi"; import { TRPCError } from "@trpc/server"; import { eq, sql } from "drizzle-orm"; -import { dump, load } from "js-yaml"; import { scheduledJobs, scheduleJob } from "node-schedule"; +import { parse, stringify } from "yaml"; import { z } from "zod"; import { db } from "@/server/db"; import { @@ -657,7 +657,7 @@ export const settingsRouter = createTRPCRouter({ const config = readMainConfig(); if (!config) return false; - const parsedConfig = load(config) as { + const parsedConfig = parse(config) as { accessLog?: { filePath: string; }; @@ -678,7 +678,7 @@ export const settingsRouter = createTRPCRouter({ const mainConfig = readMainConfig(); if (!mainConfig) return false; - const currentConfig = load(mainConfig) as { + const currentConfig = parse(mainConfig) as { accessLog?: { filePath: string; }; @@ -701,7 +701,7 @@ export const settingsRouter = createTRPCRouter({ currentConfig.accessLog = undefined; } - writeMainConfig(dump(currentConfig)); + writeMainConfig(stringify(currentConfig)); return true; }), diff --git a/apps/dokploy/server/api/routers/user.ts b/apps/dokploy/server/api/routers/user.ts index 2e7c7a0c5..d30b99b3a 100644 --- a/apps/dokploy/server/api/routers/user.ts +++ b/apps/dokploy/server/api/routers/user.ts @@ -192,7 +192,16 @@ export const userRouter = createTRPCRouter({ }) .where(eq(account.userId, ctx.user.id)); } - return await updateUser(ctx.user.id, input); + + try { + return await updateUser(ctx.user.id, input); + } catch (error) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: + error instanceof Error ? error.message : "Failed to update user", + }); + } }), getUserByToken: publicProcedure .input(apiFindOneToken) diff --git a/apps/dokploy/server/utils/deploy.ts b/apps/dokploy/server/utils/deploy.ts index df8fc8041..f4591e3b3 100644 --- a/apps/dokploy/server/utils/deploy.ts +++ b/apps/dokploy/server/utils/deploy.ts @@ -23,3 +23,30 @@ export const deploy = async (jobData: DeploymentJob) => { throw error; } }; + +type CancelDeploymentData = + | { applicationId: string; applicationType: "application" } + | { composeId: string; applicationType: "compose" }; + +export const cancelDeployment = async (cancelData: CancelDeploymentData) => { + try { + const result = await fetch(`${process.env.SERVER_URL}/cancel-deployment`, { + method: "POST", + headers: { + "Content-Type": "application/json", + "X-API-Key": process.env.API_KEY || "NO-DEFINED", + }, + body: JSON.stringify(cancelData), + }); + + if (!result.ok) { + const errorData = await result.json().catch(() => ({})); + throw new Error(errorData.message || "Failed to cancel deployment"); + } + + const data = await result.json(); + return data; + } catch (error) { + throw error; + } +}; diff --git a/apps/dokploy/server/wss/terminal.ts b/apps/dokploy/server/wss/terminal.ts index c24be3122..fa37d492b 100644 --- a/apps/dokploy/server/wss/terminal.ts +++ b/apps/dokploy/server/wss/terminal.ts @@ -1,5 +1,10 @@ import type http from "node:http"; -import { findServerById, IS_CLOUD, validateRequest } from "@dokploy/server"; +import { + execAsync, + findServerById, + IS_CLOUD, + validateRequest, +} from "@dokploy/server"; import { publicIpv4, publicIpv6 } from "public-ip"; import { Client, type ConnectConfig } from "ssh2"; import { WebSocketServer } from "ws"; @@ -44,6 +49,21 @@ export const getPublicIpWithFallback = async () => { return ip; }; +export const getLocalServerIp = async () => { + try { + const command = `ip addr show | grep -E "inet (192\.168\.|10\.|172\.1[6-9]\.|172\.2[0-9]\.|172\.3[0-1]\.)" | head -n1 | awk '{print $2}' | cut -d/ -f1`; + const { stdout } = await execAsync(command); + const ip = stdout.trim(); + return ( + ip || + "We were unable to obtain the local server IP, please use your private IP address" + ); + } catch (error) { + console.error("Error to obtain local server IP", error); + return "We were unable to obtain the local server IP, please use your private IP address"; + } +}; + export const setupTerminalWebSocketServer = ( server: http.Server, ) => { diff --git a/apps/dokploy/setup.ts b/apps/dokploy/setup.ts index 13590e4e7..55e1da87c 100644 --- a/apps/dokploy/setup.ts +++ b/apps/dokploy/setup.ts @@ -1,3 +1,4 @@ +import { exit } from "node:process"; import { execAsync } from "@dokploy/server"; import { setupDirectories } from "@dokploy/server/setup/config-paths"; import { initializePostgres } from "@dokploy/server/setup/postgres-setup"; @@ -25,6 +26,8 @@ import { await initializeStandaloneTraefik(); await initializeRedis(); await initializePostgres(); + console.log("Dokploy setup completed"); + exit(0); } catch (e) { console.error("Error in dokploy setup", e); } diff --git a/apps/schedules/src/index.ts b/apps/schedules/src/index.ts index af8ad8ff9..c37deac56 100644 --- a/apps/schedules/src/index.ts +++ b/apps/schedules/src/index.ts @@ -11,7 +11,7 @@ import { } from "./queue.js"; import { jobQueueSchema } from "./schema.js"; import { initializeJobs } from "./utils.js"; -import { firstWorker, secondWorker } from "./workers.js"; +import { firstWorker, secondWorker, thirdWorker } from "./workers.js"; const app = new Hono(); @@ -91,6 +91,7 @@ export const gracefulShutdown = async (signal: string) => { logger.warn(`Received ${signal}, closing server...`); await firstWorker.close(); await secondWorker.close(); + await thirdWorker.close(); process.exit(0); }; diff --git a/apps/schedules/src/workers.ts b/apps/schedules/src/workers.ts index 1a6d24705..37f24b38d 100644 --- a/apps/schedules/src/workers.ts +++ b/apps/schedules/src/workers.ts @@ -7,22 +7,34 @@ import { runJobs } from "./utils.js"; export const firstWorker = new Worker( "backupQueue", async (job: Job) => { - logger.info({ data: job.data }, "Running job"); + logger.info({ data: job.data }, "Running job first worker"); await runJobs(job.data); }, { - concurrency: 50, + concurrency: 100, connection, }, ); export const secondWorker = new Worker( "backupQueue", async (job: Job) => { - logger.info({ data: job.data }, "Running job"); + logger.info({ data: job.data }, "Running job second worker"); await runJobs(job.data); }, { - concurrency: 50, + concurrency: 100, + connection, + }, +); + +export const thirdWorker = new Worker( + "backupQueue", + async (job: Job) => { + logger.info({ data: job.data }, "Running job third worker"); + await runJobs(job.data); + }, + { + concurrency: 100, connection, }, ); diff --git a/packages/server/package.json b/packages/server/package.json index 3b249a65b..4d0f2e804 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -57,7 +57,7 @@ "drizzle-orm": "^0.39.3", "drizzle-zod": "0.5.1", "hi-base32": "^0.5.1", - "js-yaml": "4.1.0", + "yaml": "2.8.1", "lodash": "4.17.21", "micromatch": "4.0.8", "nanoid": "3.3.11", @@ -85,7 +85,6 @@ "@types/adm-zip": "^0.5.7", "@types/bcrypt": "5.0.2", "@types/dockerode": "3.3.23", - "@types/js-yaml": "4.0.9", "@types/lodash": "4.17.4", "@types/micromatch": "4.0.9", "@types/node": "^18.19.104", diff --git a/packages/server/src/db/schema/application.ts b/packages/server/src/db/schema/application.ts index 42e958e4e..6d176e737 100644 --- a/packages/server/src/db/schema/application.ts +++ b/packages/server/src/db/schema/application.ts @@ -328,6 +328,26 @@ export const apiFindOneApplication = createSchema }) .required(); +export const apiDeployApplication = createSchema + .pick({ + applicationId: true, + }) + .extend({ + applicationId: z.string().min(1), + title: z.string().optional(), + description: z.string().optional(), + }); + +export const apiRedeployApplication = createSchema + .pick({ + applicationId: true, + }) + .extend({ + applicationId: z.string().min(1), + title: z.string().optional(), + description: z.string().optional(), + }); + export const apiReloadApplication = createSchema .pick({ appName: true, diff --git a/packages/server/src/db/schema/compose.ts b/packages/server/src/db/schema/compose.ts index 2d75e511a..958c2c32c 100644 --- a/packages/server/src/db/schema/compose.ts +++ b/packages/server/src/db/schema/compose.ts @@ -181,6 +181,18 @@ export const apiFindCompose = z.object({ composeId: z.string().min(1), }); +export const apiDeployCompose = z.object({ + composeId: z.string().min(1), + title: z.string().optional(), + description: z.string().optional(), +}); + +export const apiRedeployCompose = z.object({ + composeId: z.string().min(1), + title: z.string().optional(), + description: z.string().optional(), +}); + export const apiDeleteCompose = z.object({ composeId: z.string().min(1), deleteVolumes: z.boolean(), diff --git a/packages/server/src/db/schema/github.ts b/packages/server/src/db/schema/github.ts index b8f739ceb..fb8a267a7 100644 --- a/packages/server/src/db/schema/github.ts +++ b/packages/server/src/db/schema/github.ts @@ -58,4 +58,5 @@ export const apiUpdateGithub = createSchema.extend({ githubId: z.string().min(1), name: z.string().min(1), gitProviderId: z.string().min(1), + githubAppName: z.string().min(1), }); diff --git a/packages/server/src/db/schema/notification.ts b/packages/server/src/db/schema/notification.ts index 1c8a2d8f3..b5e871c35 100644 --- a/packages/server/src/db/schema/notification.ts +++ b/packages/server/src/db/schema/notification.ts @@ -11,6 +11,7 @@ export const notificationType = pgEnum("notificationType", [ "discord", "email", "gotify", + "ntfy", ]); export const notifications = pgTable("notification", { @@ -44,6 +45,9 @@ export const notifications = pgTable("notification", { gotifyId: text("gotifyId").references(() => gotify.gotifyId, { onDelete: "cascade", }), + ntfyId: text("ntfyId").references(() => ntfy.ntfyId, { + onDelete: "cascade", + }), organizationId: text("organizationId") .notNull() .references(() => organization.id, { onDelete: "cascade" }), @@ -101,6 +105,17 @@ export const gotify = pgTable("gotify", { decoration: boolean("decoration"), }); +export const ntfy = pgTable("ntfy", { + ntfyId: text("ntfyId") + .notNull() + .primaryKey() + .$defaultFn(() => nanoid()), + serverUrl: text("serverUrl").notNull(), + topic: text("topic").notNull(), + accessToken: text("accessToken").notNull(), + priority: integer("priority").notNull().default(3), +}); + export const notificationsRelations = relations(notifications, ({ one }) => ({ slack: one(slack, { fields: [notifications.slackId], @@ -122,6 +137,10 @@ export const notificationsRelations = relations(notifications, ({ one }) => ({ fields: [notifications.gotifyId], references: [gotify.gotifyId], }), + ntfy: one(ntfy, { + fields: [notifications.ntfyId], + references: [ntfy.ntfyId], + }), organization: one(organization, { fields: [notifications.organizationId], references: [organization.id], @@ -284,6 +303,36 @@ export const apiTestGotifyConnection = apiCreateGotify decoration: z.boolean().optional(), }); +export const apiCreateNtfy = notificationsSchema + .pick({ + appBuildError: true, + databaseBackup: true, + dokployRestart: true, + name: true, + appDeploy: true, + dockerCleanup: true, + }) + .extend({ + serverUrl: z.string().min(1), + topic: z.string().min(1), + accessToken: z.string().min(1), + priority: z.number().min(1), + }) + .required(); + +export const apiUpdateNtfy = apiCreateNtfy.partial().extend({ + notificationId: z.string().min(1), + ntfyId: z.string().min(1), + organizationId: z.string().optional(), +}); + +export const apiTestNtfyConnection = apiCreateNtfy.pick({ + serverUrl: true, + topic: true, + accessToken: true, + priority: true, +}); + export const apiFindOneNotification = notificationsSchema .pick({ notificationId: true, @@ -303,7 +352,9 @@ export const apiSendTest = notificationsSchema password: z.string(), toAddresses: z.array(z.string()), serverUrl: z.string(), + topic: z.string(), appToken: z.string(), + accessToken: z.string(), priority: z.number(), }) .partial(); diff --git a/packages/server/src/db/schema/user.ts b/packages/server/src/db/schema/user.ts index 933a7490c..ca92f50e8 100644 --- a/packages/server/src/db/schema/user.ts +++ b/packages/server/src/db/schema/user.ts @@ -322,6 +322,11 @@ export const apiUpdateWebServerMonitoring = z.object({ }); export const apiUpdateUser = createSchema.partial().extend({ + email: z + .string() + .email("Please enter a valid email address") + .min(1, "Email is required") + .optional(), password: z.string().optional(), currentPassword: z.string().optional(), name: z.string().optional(), diff --git a/packages/server/src/services/ai.ts b/packages/server/src/services/ai.ts index 61c448397..7056f1547 100644 --- a/packages/server/src/services/ai.ts +++ b/packages/server/src/services/ai.ts @@ -92,31 +92,48 @@ export const suggestVariants = async ({ const { object } = await generateObject({ model, - output: "array", + output: "object", schema: z.object({ - id: z.string(), - name: z.string(), - shortDescription: z.string(), - description: z.string(), + suggestions: z.array( + z.object({ + id: z.string(), + name: z.string(), + shortDescription: z.string(), + description: z.string(), + }), + ), }), prompt: ` - Act as advanced DevOps engineer and generate a list of open source projects what can cover users needs(up to 3 items), the suggestion - should include id, name, shortDescription, and description. Use slug of title for id. + Act as advanced DevOps engineer and generate a list of open source projects what can cover users needs(up to 3 items). + + Return your response as a JSON object with the following structure: + { + "suggestions": [ + { + "id": "project-slug", + "name": "Project Name", + "shortDescription": "Brief one-line description", + "description": "Detailed description" + } + ] + } Important rules for the response: - 1. The description field should ONLY contain a plain text description of the project, its features, and use cases - 2. Do NOT include any code snippets, configuration examples, or installation instructions in the description - 3. The shortDescription should be a single-line summary focusing on the main technologies + 1. Use slug format for the id field (lowercase, hyphenated) + 2. The description field should ONLY contain a plain text description of the project, its features, and use cases + 3. Do NOT include any code snippets, configuration examples, or installation instructions in the description + 4. The shortDescription should be a single-line summary focusing on the main technologies + 5. All projects should be installable in docker and have docker compose support - User wants to create a new project with the following details, it should be installable in docker and can be docker compose generated for it: + User wants to create a new project with the following details: ${input} `, }); - if (object?.length) { + if (object?.suggestions?.length) { const result = []; - for (const suggestion of object) { + for (const suggestion of object.suggestions) { try { const { object: docker } = await generateObject({ model, @@ -136,16 +153,29 @@ export const suggestVariants = async ({ serviceName: z.string(), }), ), - configFiles: z.array( - z.object({ - content: z.string(), - filePath: z.string(), - }), - ), + configFiles: z + .array( + z.object({ + content: z.string(), + filePath: z.string(), + }), + ) + .optional(), }), prompt: ` Act as advanced DevOps engineer and generate docker compose with environment variables and domain configurations needed to install the following project. - Return the docker compose as a YAML string and environment variables configuration. Follow these rules: + + Return your response as a JSON object with this structure: + { + "dockerCompose": "yaml string here", + "envVariables": [{"name": "VAR_NAME", "value": "example_value"}], + "domains": [{"host": "domain.com", "port": 3000, "serviceName": "service"}], + "configFiles": [{"content": "file content", "filePath": "path/to/file"}] + } + + Note: configFiles is optional - only include it if configuration files are absolutely required. + + Follow these rules: Docker Compose Rules: 1. Use placeholder like \${VARIABLE_NAME-default} for generated variables in the docker-compose.yml @@ -198,6 +228,7 @@ export const suggestVariants = async ({ console.error("Error in docker compose generation:", error); } } + return result; } diff --git a/packages/server/src/services/certificate.ts b/packages/server/src/services/certificate.ts index f59f1c2aa..8707d098a 100644 --- a/packages/server/src/services/certificate.ts +++ b/packages/server/src/services/certificate.ts @@ -9,7 +9,7 @@ import { import { removeDirectoryIfExistsContent } from "@dokploy/server/utils/filesystem/directory"; import { TRPCError } from "@trpc/server"; import { eq } from "drizzle-orm"; -import { dump } from "js-yaml"; +import { stringify } from "yaml"; import type { z } from "zod"; import { encodeBase64 } from "../utils/docker/utils"; import { execAsyncRemote } from "../utils/process/execAsync"; @@ -101,7 +101,7 @@ const createCertificateFiles = async (certificate: Certificate) => { ], }, }; - const yamlConfig = dump(traefikConfig); + const yamlConfig = stringify(traefikConfig); const configFile = path.join(certDir, "certificate.yml"); if (certificate.serverId) { diff --git a/packages/server/src/services/notification.ts b/packages/server/src/services/notification.ts index 16ba2085b..efd52061c 100644 --- a/packages/server/src/services/notification.ts +++ b/packages/server/src/services/notification.ts @@ -3,17 +3,20 @@ import { type apiCreateDiscord, type apiCreateEmail, type apiCreateGotify, + type apiCreateNtfy, type apiCreateSlack, type apiCreateTelegram, type apiUpdateDiscord, type apiUpdateEmail, type apiUpdateGotify, + type apiUpdateNtfy, type apiUpdateSlack, type apiUpdateTelegram, discord, email, gotify, notifications, + ntfy, slack, telegram, } from "@dokploy/server/db/schema"; @@ -482,6 +485,96 @@ export const updateGotifyNotification = async ( }); }; +export const createNtfyNotification = async ( + input: typeof apiCreateNtfy._type, + organizationId: string, +) => { + await db.transaction(async (tx) => { + const newNtfy = await tx + .insert(ntfy) + .values({ + serverUrl: input.serverUrl, + topic: input.topic, + accessToken: input.accessToken, + priority: input.priority, + }) + .returning() + .then((value) => value[0]); + + if (!newNtfy) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: "Error input: Inserting ntfy", + }); + } + + const newDestination = await tx + .insert(notifications) + .values({ + ntfyId: newNtfy.ntfyId, + name: input.name, + appDeploy: input.appDeploy, + appBuildError: input.appBuildError, + databaseBackup: input.databaseBackup, + dokployRestart: input.dokployRestart, + dockerCleanup: input.dockerCleanup, + notificationType: "ntfy", + organizationId: organizationId, + }) + .returning() + .then((value) => value[0]); + + if (!newDestination) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: "Error input: Inserting notification", + }); + } + + return newDestination; + }); +}; + +export const updateNtfyNotification = async ( + input: typeof apiUpdateNtfy._type, +) => { + await db.transaction(async (tx) => { + const newDestination = await tx + .update(notifications) + .set({ + name: input.name, + appDeploy: input.appDeploy, + appBuildError: input.appBuildError, + databaseBackup: input.databaseBackup, + dokployRestart: input.dokployRestart, + dockerCleanup: input.dockerCleanup, + organizationId: input.organizationId, + }) + .where(eq(notifications.notificationId, input.notificationId)) + .returning() + .then((value) => value[0]); + + if (!newDestination) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: "Error Updating notification", + }); + } + + await tx + .update(ntfy) + .set({ + serverUrl: input.serverUrl, + topic: input.topic, + accessToken: input.accessToken, + priority: input.priority, + }) + .where(eq(ntfy.ntfyId, input.ntfyId)); + + return newDestination; + }); +}; + export const findNotificationById = async (notificationId: string) => { const notification = await db.query.notifications.findFirst({ where: eq(notifications.notificationId, notificationId), @@ -491,6 +584,7 @@ export const findNotificationById = async (notificationId: string) => { discord: true, email: true, gotify: true, + ntfy: true, }, }); if (!notification) { diff --git a/packages/server/src/services/registry.ts b/packages/server/src/services/registry.ts index 6468cd970..ec8db8fa8 100644 --- a/packages/server/src/services/registry.ts +++ b/packages/server/src/services/registry.ts @@ -10,6 +10,22 @@ import { IS_CLOUD } from "../constants"; export type Registry = typeof registry.$inferSelect; +function shEscape(s: string | undefined): string { + if (!s) return "''"; + return `'${s.replace(/'/g, `'\\''`)}'`; +} + +function safeDockerLoginCommand( + registry: string | undefined, + user: string | undefined, + pass: string | undefined, +) { + const escapedRegistry = shEscape(registry); + const escapedUser = shEscape(user); + const escapedPassword = shEscape(pass); + return `printf %s ${escapedPassword} | docker login ${escapedRegistry} -u ${escapedUser} --password-stdin`; +} + export const createRegistry = async ( input: typeof apiCreateRegistry._type, organizationId: string, @@ -37,7 +53,11 @@ export const createRegistry = async ( message: "Select a server to add the registry", }); } - const loginCommand = `echo ${input.password} | docker login ${input.registryUrl} --username ${input.username} --password-stdin`; + const loginCommand = safeDockerLoginCommand( + input.registryUrl, + input.username, + input.password, + ); if (input.serverId && input.serverId !== "none") { await execAsyncRemote(input.serverId, loginCommand); } else if (newRegistry.registryType === "cloud") { @@ -91,7 +111,11 @@ export const updateRegistry = async ( .returning() .then((res) => res[0]); - const loginCommand = `echo ${response?.password} | docker login ${response?.registryUrl} --username ${response?.username} --password-stdin`; + const loginCommand = safeDockerLoginCommand( + response?.registryUrl, + response?.username, + response?.password, + ); if ( IS_CLOUD && diff --git a/packages/server/src/services/settings.ts b/packages/server/src/services/settings.ts index f39064523..301573cb4 100644 --- a/packages/server/src/services/settings.ts +++ b/packages/server/src/services/settings.ts @@ -342,6 +342,8 @@ export const readPorts = async ( command = `docker service inspect ${resourceName} --format '{{json .Spec.EndpointSpec.Ports}}'`; } else if (resourceType === "standalone") { command = `docker container inspect ${resourceName} --format '{{json .NetworkSettings.Ports}}'`; + } else { + throw new Error("Resource type not found"); } let result = ""; if (serverId) { @@ -397,17 +399,20 @@ export const writeTraefikSetup = async (input: TraefikOptions) => { "dokploy-traefik", input.serverId, ); + if (resourceType === "service") { await initializeTraefikService({ env: input.env, additionalPorts: input.additionalPorts, serverId: input.serverId, }); - } else { + } else if (resourceType === "standalone") { await initializeStandaloneTraefik({ env: input.env, additionalPorts: input.additionalPorts, serverId: input.serverId, }); + } else { + throw new Error("Traefik resource type not found"); } }; diff --git a/packages/server/src/services/user.ts b/packages/server/src/services/user.ts index 728d5b8ee..ae03432a1 100644 --- a/packages/server/src/services/user.ts +++ b/packages/server/src/services/user.ts @@ -296,6 +296,19 @@ export const findMemberById = async ( }; export const updateUser = async (userId: string, userData: Partial) => { + // Validate email if it's being updated + if (userData.email !== undefined) { + if (!userData.email || userData.email.trim() === "") { + throw new Error("Email is required and cannot be empty"); + } + + // Basic email format validation + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(userData.email)) { + throw new Error("Please enter a valid email address"); + } + } + const user = await db .update(users_temp) .set({ diff --git a/packages/server/src/setup/traefik-setup.ts b/packages/server/src/setup/traefik-setup.ts index 17c48d0ff..fa9bf78d0 100644 --- a/packages/server/src/setup/traefik-setup.ts +++ b/packages/server/src/setup/traefik-setup.ts @@ -1,7 +1,14 @@ -import { chmodSync, existsSync, mkdirSync, writeFileSync } from "node:fs"; +import { + chmodSync, + existsSync, + mkdirSync, + rmSync, + statSync, + writeFileSync, +} from "node:fs"; import path from "node:path"; import type { ContainerCreateOptions, CreateServiceOptions } from "dockerode"; -import { dump } from "js-yaml"; +import { stringify } from "yaml"; import { paths } from "../constants"; import { getRemoteDocker } from "../utils/servers/remote-docker"; import type { FileConfig } from "../utils/traefik/file-types"; @@ -87,16 +94,27 @@ export const initializeStandaloneTraefik = async ({ }; const docker = await getRemoteDocker(serverId); + try { + await docker.pull(imageName); + await new Promise((resolve) => setTimeout(resolve, 3000)); + console.log("Traefik Image Pulled ✅"); + } catch (error) { + console.log("Traefik Image Not Found: Pulling ", error); + } try { const container = docker.getContainer(containerName); await container.remove({ force: true }); await new Promise((resolve) => setTimeout(resolve, 5000)); } catch {} - await docker.createContainer(settings); - const newContainer = docker.getContainer(containerName); - await newContainer.start(); - console.log("Traefik Started ✅"); + try { + await docker.createContainer(settings); + const newContainer = docker.getContainer(containerName); + await newContainer.start(); + console.log("Traefik Started ✅"); + } catch (error) { + console.log("Traefik Not Found: Starting ", error); + } }; export const initializeTraefikService = async ({ @@ -223,7 +241,7 @@ export const createDefaultServerTraefikConfig = () => { }, }; - const yamlStr = dump(config); + const yamlStr = stringify(config); mkdirSync(DYNAMIC_TRAEFIK_PATH, { recursive: true }); writeFileSync( path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`), @@ -297,7 +315,7 @@ export const getDefaultTraefikConfig = () => { }), }; - const yamlStr = dump(configObject); + const yamlStr = stringify(configObject); return yamlStr; }; @@ -351,7 +369,7 @@ export const getDefaultServerTraefikConfig = () => { }, }; - const yamlStr = dump(configObject); + const yamlStr = stringify(configObject); return yamlStr; }; @@ -364,13 +382,26 @@ export const createDefaultTraefikConfig = () => { if (existsSync(acmeJsonPath)) { chmodSync(acmeJsonPath, "600"); } - if (existsSync(mainConfig)) { - console.log("Main config already exists"); - return; - } - const yamlStr = getDefaultTraefikConfig(); + + // Create the traefik directory first mkdirSync(MAIN_TRAEFIK_PATH, { recursive: true }); + + // Check if traefik.yml exists and handle the case where it might be a directory + if (existsSync(mainConfig)) { + const stats = statSync(mainConfig); + if (stats.isDirectory()) { + // If traefik.yml is a directory, remove it + console.log("Found traefik.yml as directory, removing it..."); + rmSync(mainConfig, { recursive: true, force: true }); + } else if (stats.isFile()) { + console.log("Main config already exists"); + return; + } + } + + const yamlStr = getDefaultTraefikConfig(); writeFileSync(mainConfig, yamlStr, "utf8"); + console.log("Traefik config created successfully"); }; export const getDefaultMiddlewares = () => { @@ -386,7 +417,7 @@ export const getDefaultMiddlewares = () => { }, }, }; - const yamlStr = dump(defaultMiddlewares); + const yamlStr = stringify(defaultMiddlewares); return yamlStr; }; export const createDefaultMiddlewares = () => { diff --git a/packages/server/src/utils/backups/utils.ts b/packages/server/src/utils/backups/utils.ts index b4563287b..d9d5150b9 100644 --- a/packages/server/src/utils/backups/utils.ts +++ b/packages/server/src/utils/backups/utils.ts @@ -89,7 +89,7 @@ export const getMariadbBackupCommand = ( databaseUser: string, databasePassword: string, ) => { - return `docker exec -i $CONTAINER_ID bash -c "set -o pipefail; mariadb-dump --user='${databaseUser}' --password='${databasePassword}' --databases ${database} | gzip"`; + return `docker exec -i $CONTAINER_ID bash -c "set -o pipefail; mariadb-dump --user='${databaseUser}' --password='${databasePassword}' --single-transaction --quick --databases ${database} | gzip"`; }; export const getMysqlBackupCommand = ( diff --git a/packages/server/src/utils/builders/index.ts b/packages/server/src/utils/builders/index.ts index 492f799a8..5d3261dbb 100644 --- a/packages/server/src/utils/builders/index.ts +++ b/packages/server/src/utils/builders/index.ts @@ -220,8 +220,8 @@ const getImageName = (application: ApplicationNested) => { if (registry) { const { registryUrl, imagePrefix, username } = registry; const registryTag = imagePrefix - ? `${registryUrl}/${imagePrefix}/${imageName}` - : `${registryUrl}/${username}/${imageName}`; + ? `${registryUrl ? `${registryUrl}/` : ""}${imagePrefix}/${imageName}` + : `${registryUrl ? `${registryUrl}/` : ""}${username}/${imageName}`; return registryTag; } diff --git a/packages/server/src/utils/cluster/upload.ts b/packages/server/src/utils/cluster/upload.ts index f982d414b..c13a2701c 100644 --- a/packages/server/src/utils/cluster/upload.ts +++ b/packages/server/src/utils/cluster/upload.ts @@ -22,8 +22,8 @@ export const uploadImage = async ( // For ghcr.io: ghcr.io/username/image:tag // For docker.io: docker.io/username/image:tag const registryTag = imagePrefix - ? `${registryUrl}/${imagePrefix}/${imageName}` - : `${registryUrl}/${username}/${imageName}`; + ? `${registryUrl ? `${registryUrl}/` : ""}${imagePrefix}/${imageName}` + : `${registryUrl ? `${registryUrl}/` : ""}${username}/${imageName}`; try { writeStream.write( diff --git a/packages/server/src/utils/docker/collision.ts b/packages/server/src/utils/docker/collision.ts index de6d9bbb3..9752100ca 100644 --- a/packages/server/src/utils/docker/collision.ts +++ b/packages/server/src/utils/docker/collision.ts @@ -1,5 +1,5 @@ import { findComposeById } from "@dokploy/server/services/compose"; -import { dump } from "js-yaml"; +import { stringify } from "yaml"; import { addAppNameToAllServiceNames } from "./collision/root-network"; import { generateRandomHash } from "./compose"; import { addSuffixToAllVolumes } from "./compose/volume"; @@ -59,7 +59,7 @@ export const randomizeIsolatedDeploymentComposeFile = async ( ) : composeData; - return dump(newComposeFile); + return stringify(newComposeFile); }; export const randomizeDeployableSpecificationFile = ( diff --git a/packages/server/src/utils/docker/compose.ts b/packages/server/src/utils/docker/compose.ts index 2cb909abb..a78b416ec 100644 --- a/packages/server/src/utils/docker/compose.ts +++ b/packages/server/src/utils/docker/compose.ts @@ -1,6 +1,6 @@ import crypto from "node:crypto"; import { findComposeById } from "@dokploy/server/services/compose"; -import { dump, load } from "js-yaml"; +import { parse, stringify } from "yaml"; import { addSuffixToAllConfigs } from "./compose/configs"; import { addSuffixToAllNetworks } from "./compose/network"; import { addSuffixToAllSecrets } from "./compose/secrets"; @@ -18,13 +18,13 @@ export const randomizeComposeFile = async ( ) => { const compose = await findComposeById(composeId); const composeFile = compose.composeFile; - const composeData = load(composeFile) as ComposeSpecification; + const composeData = parse(composeFile) as ComposeSpecification; const randomSuffix = suffix || generateRandomHash(); const newComposeFile = addSuffixToAllProperties(composeData, randomSuffix); - return dump(newComposeFile); + return stringify(newComposeFile); }; export const randomizeSpecificationFile = ( diff --git a/packages/server/src/utils/docker/domain.ts b/packages/server/src/utils/docker/domain.ts index 4bd38f874..7a9521d1d 100644 --- a/packages/server/src/utils/docker/domain.ts +++ b/packages/server/src/utils/docker/domain.ts @@ -4,7 +4,7 @@ import { join } from "node:path"; import { paths } from "@dokploy/server/constants"; import type { Compose } from "@dokploy/server/services/compose"; import type { Domain } from "@dokploy/server/services/domain"; -import { dump, load } from "js-yaml"; +import { parse, stringify } from "yaml"; import { execAsyncRemote } from "../process/execAsync"; import { cloneRawBitbucketRepository, @@ -92,7 +92,7 @@ export const loadDockerCompose = async ( if (existsSync(path)) { const yamlStr = readFileSync(path, "utf8"); - const parsedConfig = load(yamlStr) as ComposeSpecification; + const parsedConfig = parse(yamlStr) as ComposeSpecification; return parsedConfig; } return null; @@ -115,7 +115,7 @@ export const loadDockerComposeRemote = async ( return null; } if (!stdout) return null; - const parsedConfig = load(stdout) as ComposeSpecification; + const parsedConfig = parse(stdout) as ComposeSpecification; return parsedConfig; } catch { return null; @@ -141,7 +141,7 @@ export const writeDomainsToCompose = async ( const composeConverted = await addDomainToCompose(compose, domains); const path = getComposePath(compose); - const composeString = dump(composeConverted, { lineWidth: 1000 }); + const composeString = stringify(composeConverted, { lineWidth: 1000 }); try { await writeFile(path, composeString, "utf8"); } catch (error) { @@ -169,7 +169,7 @@ exit 1; `; } if (compose.serverId) { - const composeString = dump(composeConverted, { lineWidth: 1000 }); + const composeString = stringify(composeConverted, { lineWidth: 1000 }); const encodedContent = encodeBase64(composeString); return `echo "${encodedContent}" | base64 -d > "${path}";`; } @@ -251,11 +251,15 @@ export const addDomainToCompose = async ( } labels.unshift(...httpLabels); if (!compose.isolatedDeployment) { - if (!labels.includes("traefik.docker.network=dokploy-network")) { - labels.unshift("traefik.docker.network=dokploy-network"); - } - if (!labels.includes("traefik.swarm.network=dokploy-network")) { - labels.unshift("traefik.swarm.network=dokploy-network"); + if (compose.composeType === "docker-compose") { + if (!labels.includes("traefik.docker.network=dokploy-network")) { + labels.unshift("traefik.docker.network=dokploy-network"); + } + } else { + // Stack Case + if (!labels.includes("traefik.swarm.network=dokploy-network")) { + labels.unshift("traefik.swarm.network=dokploy-network"); + } } } } @@ -283,7 +287,7 @@ export const writeComposeFile = async ( const path = getComposePath(compose); try { - const composeFile = dump(composeSpec, { + const composeFile = stringify(composeSpec, { lineWidth: 1000, }); fs.writeFileSync(path, composeFile, "utf8"); diff --git a/packages/server/src/utils/notifications/build-error.ts b/packages/server/src/utils/notifications/build-error.ts index 47fa4de77..0d2c2108b 100644 --- a/packages/server/src/utils/notifications/build-error.ts +++ b/packages/server/src/utils/notifications/build-error.ts @@ -8,6 +8,7 @@ import { sendDiscordNotification, sendEmailNotification, sendGotifyNotification, + sendNtfyNotification, sendSlackNotification, sendTelegramNotification, } from "./utils"; @@ -42,11 +43,12 @@ export const sendBuildErrorNotifications = async ({ telegram: true, slack: true, gotify: true, + ntfy: true, }, }); for (const notification of notificationList) { - const { email, discord, telegram, slack, gotify } = notification; + const { email, discord, telegram, slack, gotify, ntfy } = notification; if (email) { const template = await renderAsync( BuildFailedEmail({ @@ -132,6 +134,20 @@ export const sendBuildErrorNotifications = async ({ ); } + if (ntfy) { + await sendNtfyNotification( + ntfy, + "Build Failed", + "warning", + `view, Build details, ${buildLink}, clear=true;`, + `🛠️Project: ${projectName}\n` + + `⚙️Application: ${applicationName}\n` + + `❔Type: ${applicationType}\n` + + `🕒Date: ${date.toLocaleString()}\n` + + `⚠️Error:\n${errorMessage}`, + ); + } + if (telegram) { const inlineButton = [ [ diff --git a/packages/server/src/utils/notifications/build-success.ts b/packages/server/src/utils/notifications/build-success.ts index ac470c49f..fb8b89f76 100644 --- a/packages/server/src/utils/notifications/build-success.ts +++ b/packages/server/src/utils/notifications/build-success.ts @@ -9,6 +9,7 @@ import { sendDiscordNotification, sendEmailNotification, sendGotifyNotification, + sendNtfyNotification, sendSlackNotification, sendTelegramNotification, } from "./utils"; @@ -43,11 +44,12 @@ export const sendBuildSuccessNotifications = async ({ telegram: true, slack: true, gotify: true, + ntfy: true, }, }); for (const notification of notificationList) { - const { email, discord, telegram, slack, gotify } = notification; + const { email, discord, telegram, slack, gotify, ntfy } = notification; if (email) { const template = await renderAsync( @@ -126,6 +128,19 @@ export const sendBuildSuccessNotifications = async ({ ); } + if (ntfy) { + await sendNtfyNotification( + ntfy, + "Build Success", + "white_check_mark", + `view, Build details, ${buildLink}, clear=true;`, + `🛠Project: ${projectName}\n` + + `⚙️Application: ${applicationName}\n` + + `❔Type: ${applicationType}\n` + + `🕒Date: ${date.toLocaleString()}`, + ); + } + if (telegram) { const chunkArray = (array: T[], chunkSize: number): T[][] => Array.from({ length: Math.ceil(array.length / chunkSize) }, (_, i) => diff --git a/packages/server/src/utils/notifications/database-backup.ts b/packages/server/src/utils/notifications/database-backup.ts index 044e3a0cf..f3c5cd5f4 100644 --- a/packages/server/src/utils/notifications/database-backup.ts +++ b/packages/server/src/utils/notifications/database-backup.ts @@ -8,6 +8,7 @@ import { sendDiscordNotification, sendEmailNotification, sendGotifyNotification, + sendNtfyNotification, sendSlackNotification, sendTelegramNotification, } from "./utils"; @@ -42,11 +43,12 @@ export const sendDatabaseBackupNotifications = async ({ telegram: true, slack: true, gotify: true, + ntfy: true, }, }); for (const notification of notificationList) { - const { email, discord, telegram, slack, gotify } = notification; + const { email, discord, telegram, slack, gotify, ntfy } = notification; if (email) { const template = await renderAsync( @@ -149,6 +151,21 @@ export const sendDatabaseBackupNotifications = async ({ ); } + if (ntfy) { + await sendNtfyNotification( + ntfy, + `Database Backup ${type === "success" ? "Successful" : "Failed"}`, + `${type === "success" ? "white_check_mark" : "x"}`, + "", + `🛠Project: ${projectName}\n` + + `⚙️Application: ${applicationName}\n` + + `❔Type: ${databaseType}\n` + + `📂Database Name: ${databaseName}` + + `🕒Date: ${date.toLocaleString()}\n` + + `${type === "error" && errorMessage ? `❌Error:\n${errorMessage}` : ""}`, + ); + } + if (telegram) { const isError = type === "error" && errorMessage; diff --git a/packages/server/src/utils/notifications/docker-cleanup.ts b/packages/server/src/utils/notifications/docker-cleanup.ts index b3959cccd..15b1c347a 100644 --- a/packages/server/src/utils/notifications/docker-cleanup.ts +++ b/packages/server/src/utils/notifications/docker-cleanup.ts @@ -8,6 +8,7 @@ import { sendDiscordNotification, sendEmailNotification, sendGotifyNotification, + sendNtfyNotification, sendSlackNotification, sendTelegramNotification, } from "./utils"; @@ -29,11 +30,12 @@ export const sendDockerCleanupNotifications = async ( telegram: true, slack: true, gotify: true, + ntfy: true, }, }); for (const notification of notificationList) { - const { email, discord, telegram, slack, gotify } = notification; + const { email, discord, telegram, slack, gotify, ntfy } = notification; if (email) { const template = await renderAsync( @@ -93,6 +95,16 @@ export const sendDockerCleanupNotifications = async ( ); } + if (ntfy) { + await sendNtfyNotification( + ntfy, + "Docker Cleanup", + "white_check_mark", + "", + `🕒Date: ${date.toLocaleString()}\n` + `📜Message:\n${message}`, + ); + } + if (telegram) { await sendTelegramNotification( telegram, diff --git a/packages/server/src/utils/notifications/dokploy-restart.ts b/packages/server/src/utils/notifications/dokploy-restart.ts index 53ccf05d9..a6ade6f11 100644 --- a/packages/server/src/utils/notifications/dokploy-restart.ts +++ b/packages/server/src/utils/notifications/dokploy-restart.ts @@ -8,6 +8,7 @@ import { sendDiscordNotification, sendEmailNotification, sendGotifyNotification, + sendNtfyNotification, sendSlackNotification, sendTelegramNotification, } from "./utils"; @@ -23,11 +24,12 @@ export const sendDokployRestartNotifications = async () => { telegram: true, slack: true, gotify: true, + ntfy: true, }, }); for (const notification of notificationList) { - const { email, discord, telegram, slack, gotify } = notification; + const { email, discord, telegram, slack, gotify, ntfy } = notification; if (email) { const template = await renderAsync( @@ -85,6 +87,20 @@ export const sendDokployRestartNotifications = async () => { } } + if (ntfy) { + try { + await sendNtfyNotification( + ntfy, + "Dokploy Server Restarted", + "white_check_mark", + "", + `🕒Date: ${date.toLocaleString()}`, + ); + } catch (error) { + console.log(error); + } + } + if (telegram) { try { await sendTelegramNotification( diff --git a/packages/server/src/utils/notifications/utils.ts b/packages/server/src/utils/notifications/utils.ts index dd552cf3e..ec1c020ad 100644 --- a/packages/server/src/utils/notifications/utils.ts +++ b/packages/server/src/utils/notifications/utils.ts @@ -2,6 +2,7 @@ import type { discord, email, gotify, + ntfy, slack, telegram, } from "@dokploy/server/db/schema"; @@ -126,3 +127,27 @@ export const sendGotifyNotification = async ( ); } }; + +export const sendNtfyNotification = async ( + connection: typeof ntfy.$inferInsert, + title: string, + tags: string, + actions: string, + message: string, +) => { + const response = await fetch(`${connection.serverUrl}/${connection.topic}`, { + method: "POST", + headers: { + Authorization: `Bearer ${connection.accessToken}`, + "X-Priority": connection.priority?.toString() || "3", + "X-Title": title, + "X-Tags": tags, + "X-Actions": actions, + }, + body: message, + }); + + if (!response.ok) { + throw new Error(`Failed to send ntfy notification: ${response.statusText}`); + } +}; diff --git a/packages/server/src/utils/providers/github.ts b/packages/server/src/utils/providers/github.ts index b13bbfcc4..30125db8b 100644 --- a/packages/server/src/utils/providers/github.ts +++ b/packages/server/src/utils/providers/github.ts @@ -171,7 +171,7 @@ export const cloneGithubRepository = async ({ const cloneUrl = `https://oauth2:${token}@${repoclone}`; try { - writeStream.write(`\nClonning Repo ${repoclone} to ${outputPath}: ✅\n`); + writeStream.write(`\nCloning Repo ${repoclone} to ${outputPath}: ✅\n`); const cloneArgs = [ "clone", "--branch", diff --git a/packages/server/src/utils/providers/gitlab.ts b/packages/server/src/utils/providers/gitlab.ts index 4b6bb7965..840347fdb 100644 --- a/packages/server/src/utils/providers/gitlab.ts +++ b/packages/server/src/utils/providers/gitlab.ts @@ -401,7 +401,7 @@ export const cloneRawGitlabRepositoryRemote = async (compose: Compose) => { const { appName, gitlabPathNamespace, - branch, + gitlabBranch, gitlabId, serverId, enableSubmodules, @@ -429,7 +429,7 @@ export const cloneRawGitlabRepositoryRemote = async (compose: Compose) => { try { const command = ` rm -rf ${outputPath}; - git clone --branch ${branch} --depth 1 ${enableSubmodules ? "--recurse-submodules" : ""} ${cloneUrl} ${outputPath} + git clone --branch ${gitlabBranch} --depth 1 ${enableSubmodules ? "--recurse-submodules" : ""} ${cloneUrl} ${outputPath} `; await execAsyncRemote(serverId, command); } catch (error) { diff --git a/packages/server/src/utils/traefik/application.ts b/packages/server/src/utils/traefik/application.ts index cd2c30a1f..b18161c76 100644 --- a/packages/server/src/utils/traefik/application.ts +++ b/packages/server/src/utils/traefik/application.ts @@ -3,7 +3,7 @@ import path from "node:path"; import { createInterface } from "node:readline"; import { paths } from "@dokploy/server/constants"; import type { Domain } from "@dokploy/server/services/domain"; -import { dump, load } from "js-yaml"; +import { parse, stringify } from "yaml"; import { encodeBase64 } from "../docker/utils"; import { execAsyncRemote } from "../process/execAsync"; import type { FileConfig, HttpLoadBalancerService } from "./file-types"; @@ -40,7 +40,7 @@ export const createTraefikConfig = (appName: string) => { }, }, }; - const yamlStr = dump(config); + const yamlStr = stringify(config); const { DYNAMIC_TRAEFIK_PATH } = paths(); fs.mkdirSync(DYNAMIC_TRAEFIK_PATH, { recursive: true }); writeFileSync( @@ -87,7 +87,7 @@ export const loadOrCreateConfig = (appName: string): FileConfig => { const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`); if (fs.existsSync(configPath)) { const yamlStr = fs.readFileSync(configPath, "utf8"); - const parsedConfig = (load(yamlStr) as FileConfig) || { + const parsedConfig = (parse(yamlStr) as FileConfig) || { http: { routers: {}, services: {} }, }; return parsedConfig; @@ -107,7 +107,7 @@ export const loadOrCreateConfigRemote = async ( if (!stdout) return fileConfig; - const parsedConfig = (load(stdout) as FileConfig) || { + const parsedConfig = (parse(stdout) as FileConfig) || { http: { routers: {}, services: {} }, }; return parsedConfig; @@ -248,7 +248,7 @@ export const writeTraefikConfig = ( try { const { DYNAMIC_TRAEFIK_PATH } = paths(); const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`); - const yamlStr = dump(traefikConfig); + const yamlStr = stringify(traefikConfig); fs.writeFileSync(configPath, yamlStr, "utf8"); } catch (e) { console.error("Error saving the YAML config file:", e); @@ -263,7 +263,7 @@ export const writeTraefikConfigRemote = async ( try { const { DYNAMIC_TRAEFIK_PATH } = paths(true); const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`); - const yamlStr = dump(traefikConfig); + const yamlStr = stringify(traefikConfig); await execAsyncRemote(serverId, `echo '${yamlStr}' > ${configPath}`); } catch (e) { console.error("Error saving the YAML config file:", e); diff --git a/packages/server/src/utils/traefik/middleware.ts b/packages/server/src/utils/traefik/middleware.ts index 1254e1f51..4897b94ee 100644 --- a/packages/server/src/utils/traefik/middleware.ts +++ b/packages/server/src/utils/traefik/middleware.ts @@ -2,7 +2,7 @@ import { existsSync, readFileSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { paths } from "@dokploy/server/constants"; import type { Domain } from "@dokploy/server/services/domain"; -import { dump, load } from "js-yaml"; +import { parse, stringify } from "yaml"; import type { ApplicationNested } from "../builders"; import { execAsyncRemote } from "../process/execAsync"; import { writeTraefikConfigRemote } from "./application"; @@ -76,7 +76,7 @@ export const loadMiddlewares = () => { throw new Error(`File not found: ${configPath}`); } const yamlStr = readFileSync(configPath, "utf8"); - const config = load(yamlStr) as T; + const config = parse(yamlStr) as T; return config; }; @@ -94,7 +94,7 @@ export const loadRemoteMiddlewares = async (serverId: string) => { console.error(`Error: ${stderr}`); throw new Error(`File not found: ${configPath}`); } - const config = load(stdout) as FileConfig; + const config = parse(stdout) as FileConfig; return config; } catch (_) { throw new Error(`File not found: ${configPath}`); @@ -103,7 +103,7 @@ export const loadRemoteMiddlewares = async (serverId: string) => { export const writeMiddleware = (config: T) => { const { DYNAMIC_TRAEFIK_PATH } = paths(); const configPath = join(DYNAMIC_TRAEFIK_PATH, "middlewares.yml"); - const newYamlContent = dump(config); + const newYamlContent = stringify(config); writeFileSync(configPath, newYamlContent, "utf8"); }; diff --git a/packages/server/src/utils/traefik/web-server.ts b/packages/server/src/utils/traefik/web-server.ts index 1534e2f1c..02472a583 100644 --- a/packages/server/src/utils/traefik/web-server.ts +++ b/packages/server/src/utils/traefik/web-server.ts @@ -2,7 +2,7 @@ import { existsSync, readFileSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { paths } from "@dokploy/server/constants"; import type { User } from "@dokploy/server/services/user"; -import { dump, load } from "js-yaml"; +import { parse, stringify } from "yaml"; import { loadOrCreateConfig, removeTraefikConfig, @@ -79,13 +79,13 @@ export const updateLetsEncryptEmail = (newEmail: string | null) => { const { MAIN_TRAEFIK_PATH } = paths(); const configPath = join(MAIN_TRAEFIK_PATH, "traefik.yml"); const configContent = readFileSync(configPath, "utf8"); - const config = load(configContent) as MainTraefikConfig; + const config = parse(configContent) as MainTraefikConfig; if (config?.certificatesResolvers?.letsencrypt?.acme) { config.certificatesResolvers.letsencrypt.acme.email = newEmail; } else { throw new Error("Invalid Let's Encrypt configuration structure."); } - const newYamlContent = dump(config); + const newYamlContent = stringify(config); writeFileSync(configPath, newYamlContent, "utf8"); } catch (error) { throw error; diff --git a/packages/server/src/utils/volume-backups/utils.ts b/packages/server/src/utils/volume-backups/utils.ts index 5b55c240c..b6a34e2aa 100644 --- a/packages/server/src/utils/volume-backups/utils.ts +++ b/packages/server/src/utils/volume-backups/utils.ts @@ -1,15 +1,17 @@ -import { findVolumeBackupById } from "@dokploy/server/services/volume-backups"; -import { scheduledJobs, scheduleJob } from "node-schedule"; +import path from "node:path"; +import { paths } from "@dokploy/server/constants"; import { createDeploymentVolumeBackup, updateDeploymentStatus, } from "@dokploy/server/services/deployment"; +import { findVolumeBackupById } from "@dokploy/server/services/volume-backups"; import { execAsync, execAsyncRemote, } from "@dokploy/server/utils/process/execAsync"; -import { backupVolume } from "./backup"; +import { scheduledJobs, scheduleJob } from "node-schedule"; import { getS3Credentials, normalizeS3Path } from "../backups/utils"; +import { backupVolume } from "./backup"; export const scheduleVolumeBackup = async (volumeBackupId: string) => { const volumeBackup = await findVolumeBackupById(volumeBackupId); @@ -76,7 +78,20 @@ export const runVolumeBackup = async (volumeBackupId: string) => { await updateDeploymentStatus(deployment.deploymentId, "done"); } catch (error) { + const { VOLUME_BACKUPS_PATH } = paths(!!serverId); + const volumeBackupPath = path.join( + VOLUME_BACKUPS_PATH, + volumeBackup.appName, + ); + // delete all the .tar files + const command = `rm -rf ${volumeBackupPath}/*.tar`; + if (serverId) { + await execAsyncRemote(serverId, command); + } else { + await execAsync(command); + } await updateDeploymentStatus(deployment.deploymentId, "error"); + console.error(error); } }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 94dedb322..6df6799ac 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -325,9 +325,6 @@ importers: js-cookie: specifier: ^3.0.5 version: 3.0.5 - js-yaml: - specifier: 4.1.0 - version: 4.1.0 lodash: specifier: 4.17.21 version: 4.17.21 @@ -448,6 +445,9 @@ importers: xterm-addon-fit: specifier: ^0.8.0 version: 0.8.0(xterm@5.3.0) + yaml: + specifier: 2.8.1 + version: 2.8.1 zod: specifier: ^3.25.32 version: 3.25.32 @@ -464,9 +464,6 @@ importers: '@types/js-cookie': specifier: ^3.0.6 version: 3.0.6 - '@types/js-yaml': - specifier: 4.0.9 - version: 4.0.9 '@types/lodash': specifier: 4.17.4 version: 4.17.4 @@ -681,9 +678,6 @@ importers: hi-base32: specifier: ^0.5.1 version: 0.5.1 - js-yaml: - specifier: 4.1.0 - version: 4.1.0 lodash: specifier: 4.17.21 version: 4.17.21 @@ -747,6 +741,9 @@ importers: ws: specifier: 8.16.0 version: 8.16.0 + yaml: + specifier: 2.8.1 + version: 2.8.1 zod: specifier: ^3.25.32 version: 3.25.32 @@ -760,9 +757,6 @@ importers: '@types/dockerode': specifier: 3.3.23 version: 3.3.23 - '@types/js-yaml': - specifier: 4.0.9 - version: 4.0.9 '@types/lodash': specifier: 4.17.4 version: 4.17.4 @@ -3965,9 +3959,6 @@ packages: '@types/js-cookie@3.0.6': resolution: {integrity: sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==} - '@types/js-yaml@4.0.9': - resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==} - '@types/jsonwebtoken@9.0.9': resolution: {integrity: sha512-uoe+GxEuHbvy12OUQct2X9JenKM3qAscquYymuQN4fMWG9DBQtykrQEFcAbVACF7qaLw9BePSodUL0kquqBJpQ==} @@ -7783,6 +7774,11 @@ packages: engines: {node: '>= 14.6'} hasBin: true + yaml@2.8.1: + resolution: {integrity: sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==} + engines: {node: '>= 14.6'} + hasBin: true + yargs-parser@18.1.3: resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} engines: {node: '>=6'} @@ -11223,8 +11219,6 @@ snapshots: '@types/js-cookie@3.0.6': {} - '@types/js-yaml@4.0.9': {} - '@types/jsonwebtoken@9.0.9': dependencies: '@types/ms': 2.1.0 @@ -14032,7 +14026,7 @@ snapshots: postcss-load-config@4.0.2(postcss@8.5.3): dependencies: lilconfig: 3.1.3 - yaml: 2.8.0 + yaml: 2.8.1 optionalDependencies: postcss: 8.5.3 @@ -15353,6 +15347,8 @@ snapshots: yaml@2.8.0: {} + yaml@2.8.1: {} + yargs-parser@18.1.3: dependencies: camelcase: 5.3.1