From 6d17f629425d0c287fc89932a42e4034f8eeae2a Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Sun, 29 Mar 2026 22:02:53 +0000 Subject: [PATCH] [autofix.ci] apply automated fixes --- .../compose/network/network-service.test.ts | 206 +++--- .../__test__/env/stack-environment.test.ts | 194 ++--- packages/server/src/services/user.ts | 676 +++++++++--------- packages/server/src/utils/builders/compose.ts | 144 ++-- .../server/src/utils/builders/docker-file.ts | 130 ++-- 5 files changed, 675 insertions(+), 675 deletions(-) diff --git a/apps/dokploy/__test__/compose/network/network-service.test.ts b/apps/dokploy/__test__/compose/network/network-service.test.ts index 91eb5da4e..073e61615 100644 --- a/apps/dokploy/__test__/compose/network/network-service.test.ts +++ b/apps/dokploy/__test__/compose/network/network-service.test.ts @@ -1,7 +1,7 @@ import type { ComposeSpecification } from "@dokploy/server"; import { - addSuffixToServiceNetworks, - generateRandomHash, + addSuffixToServiceNetworks, + generateRandomHash, } from "@dokploy/server"; import { expect, test } from "vitest"; import { parse } from "yaml"; @@ -23,30 +23,30 @@ services: `; test("Add suffix to networks in services", () => { - const composeData = parse(composeFile) as ComposeSpecification; + const composeData = parse(composeFile) as ComposeSpecification; - const suffix = generateRandomHash(); + const suffix = generateRandomHash(); - if (!composeData?.services) { - return; - } - const services = addSuffixToServiceNetworks(composeData.services, suffix); - const actualComposeData = { ...composeData, services }; + if (!composeData?.services) { + return; + } + const services = addSuffixToServiceNetworks(composeData.services, suffix); + const actualComposeData = { ...composeData, services }; - expect(actualComposeData?.services?.web?.networks).toContain( - `frontend-${suffix}`, - ); + expect(actualComposeData?.services?.web?.networks).toContain( + `frontend-${suffix}`, + ); - expect(actualComposeData?.services?.api?.networks).toContain( - `backend-${suffix}`, - ); + expect(actualComposeData?.services?.api?.networks).toContain( + `backend-${suffix}`, + ); - const apiNetworks = actualComposeData?.services?.api?.networks; + const apiNetworks = actualComposeData?.services?.api?.networks; - expect(apiNetworks).toBeDefined(); - expect(actualComposeData?.services?.api?.networks).toContain( - `backend-${suffix}`, - ); + expect(apiNetworks).toBeDefined(); + expect(actualComposeData?.services?.api?.networks).toContain( + `backend-${suffix}`, + ); }); // Caso 2: Objeto con aliases @@ -67,29 +67,29 @@ networks: `; test("Add suffix to networks in services with aliases", () => { - const composeData = parse(composeFile2) as ComposeSpecification; + const composeData = parse(composeFile2) as ComposeSpecification; - const suffix = generateRandomHash(); + const suffix = generateRandomHash(); - if (!composeData?.services) { - return; - } - const services = addSuffixToServiceNetworks(composeData.services, suffix); - const actualComposeData = { ...composeData, services }; + if (!composeData?.services) { + return; + } + const services = addSuffixToServiceNetworks(composeData.services, suffix); + const actualComposeData = { ...composeData, services }; - expect(actualComposeData.services?.api?.networks).toHaveProperty( - `frontend-${suffix}`, - ); + expect(actualComposeData.services?.api?.networks).toHaveProperty( + `frontend-${suffix}`, + ); - const networkConfig = actualComposeData?.services?.api?.networks as { - [key: string]: { aliases?: string[] }; - }; - expect(networkConfig[`frontend-${suffix}`]).toBeDefined(); - expect(networkConfig[`frontend-${suffix}`]?.aliases).toContain("api"); + const networkConfig = actualComposeData?.services?.api?.networks as { + [key: string]: { aliases?: string[] }; + }; + expect(networkConfig[`frontend-${suffix}`]).toBeDefined(); + expect(networkConfig[`frontend-${suffix}`]?.aliases).toContain("api"); - expect(actualComposeData.services?.api?.networks).not.toHaveProperty( - "frontend-ash", - ); + expect(actualComposeData.services?.api?.networks).not.toHaveProperty( + "frontend-ash", + ); }); const composeFile3 = ` @@ -107,19 +107,19 @@ networks: `; test("Add suffix to networks in services (Object with simple networks)", () => { - const composeData = parse(composeFile3) as ComposeSpecification; + const composeData = parse(composeFile3) as ComposeSpecification; - const suffix = generateRandomHash(); + const suffix = generateRandomHash(); - if (!composeData?.services) { - return; - } - const services = addSuffixToServiceNetworks(composeData.services, suffix); - const actualComposeData = { ...composeData, services }; + if (!composeData?.services) { + return; + } + const services = addSuffixToServiceNetworks(composeData.services, suffix); + const actualComposeData = { ...composeData, services }; - expect(actualComposeData.services?.redis?.networks).toHaveProperty( - `backend-${suffix}`, - ); + expect(actualComposeData.services?.redis?.networks).toHaveProperty( + `backend-${suffix}`, + ); }); const composeFileCombined = ` @@ -153,36 +153,36 @@ networks: `; test("Add suffix to networks in services (combined case)", () => { - const composeData = parse(composeFileCombined) as ComposeSpecification; + const composeData = parse(composeFileCombined) as ComposeSpecification; - const suffix = generateRandomHash(); + const suffix = generateRandomHash(); - if (!composeData?.services) { - return; - } - const services = addSuffixToServiceNetworks(composeData.services, suffix); - const actualComposeData = { ...composeData, services }; + if (!composeData?.services) { + return; + } + const services = addSuffixToServiceNetworks(composeData.services, suffix); + const actualComposeData = { ...composeData, services }; - // Caso 1: ListOfStrings - expect(actualComposeData.services?.web?.networks).toContain( - `frontend-${suffix}`, - ); - expect(actualComposeData.services?.web?.networks).toContain( - `backend-${suffix}`, - ); + // Caso 1: ListOfStrings + expect(actualComposeData.services?.web?.networks).toContain( + `frontend-${suffix}`, + ); + expect(actualComposeData.services?.web?.networks).toContain( + `backend-${suffix}`, + ); - // Caso 2: Objeto con aliases - const apiNetworks = actualComposeData.services?.api?.networks as { - [key: string]: unknown; - }; - expect(apiNetworks).toHaveProperty(`frontend-${suffix}`); - expect(apiNetworks[`frontend-${suffix}`]).toBeDefined(); - expect(apiNetworks).not.toHaveProperty("frontend"); + // Caso 2: Objeto con aliases + const apiNetworks = actualComposeData.services?.api?.networks as { + [key: string]: unknown; + }; + expect(apiNetworks).toHaveProperty(`frontend-${suffix}`); + expect(apiNetworks[`frontend-${suffix}`]).toBeDefined(); + expect(apiNetworks).not.toHaveProperty("frontend"); - // Caso 3: Objeto con redes simples - const redisNetworks = actualComposeData.services?.redis?.networks; - expect(redisNetworks).toHaveProperty(`backend-${suffix}`); - expect(redisNetworks).not.toHaveProperty("backend"); + // Caso 3: Objeto con redes simples + const redisNetworks = actualComposeData.services?.redis?.networks; + expect(redisNetworks).toHaveProperty(`backend-${suffix}`); + expect(redisNetworks).not.toHaveProperty("backend"); }); const composeFile7 = ` @@ -196,18 +196,18 @@ services: `; test("It shouldn't add suffix to dokploy-network in services", () => { - const composeData = parse(composeFile7) as ComposeSpecification; + const composeData = parse(composeFile7) as ComposeSpecification; - const suffix = generateRandomHash(); + const suffix = generateRandomHash(); - if (!composeData?.services) { - return; - } - const networks = addSuffixToServiceNetworks(composeData.services, suffix); - const service = networks.web; + if (!composeData?.services) { + return; + } + const networks = addSuffixToServiceNetworks(composeData.services, suffix); + const service = networks.web; - expect(service).toBeDefined(); - expect(service?.networks).toContain("dokploy-network"); + expect(service).toBeDefined(); + expect(service?.networks).toContain("dokploy-network"); }); const composeFile8 = ` @@ -245,31 +245,31 @@ services: `; test("It shouldn't add suffix to dokploy-network in services multiples cases", () => { - const composeData = parse(composeFile8) as ComposeSpecification; + const composeData = parse(composeFile8) as ComposeSpecification; - const suffix = generateRandomHash(); + const suffix = generateRandomHash(); - if (!composeData?.services) { - return; - } - const networks = addSuffixToServiceNetworks(composeData.services, suffix); - const service = networks.web; - const api = networks.api; - const redis = networks.redis; - const db = networks.db; + if (!composeData?.services) { + return; + } + const networks = addSuffixToServiceNetworks(composeData.services, suffix); + const service = networks.web; + const api = networks.api; + const redis = networks.redis; + const db = networks.db; - const dbNetworks = db?.networks as { - [key: string]: unknown; - }; + const dbNetworks = db?.networks as { + [key: string]: unknown; + }; - const apiNetworks = api?.networks as { - [key: string]: unknown; - }; + const apiNetworks = api?.networks as { + [key: string]: unknown; + }; - expect(service).toBeDefined(); - expect(service?.networks).toContain("dokploy-network"); + expect(service).toBeDefined(); + expect(service?.networks).toContain("dokploy-network"); - expect(redis?.networks).toHaveProperty("dokploy-network"); - expect(dbNetworks["dokploy-network"]).toBeDefined(); - expect(apiNetworks["dokploy-network"]).toBeDefined(); + expect(redis?.networks).toHaveProperty("dokploy-network"); + expect(dbNetworks["dokploy-network"]).toBeDefined(); + expect(apiNetworks["dokploy-network"]).toBeDefined(); }); diff --git a/apps/dokploy/__test__/env/stack-environment.test.ts b/apps/dokploy/__test__/env/stack-environment.test.ts index cde7be9db..773adf3ed 100644 --- a/apps/dokploy/__test__/env/stack-environment.test.ts +++ b/apps/dokploy/__test__/env/stack-environment.test.ts @@ -16,28 +16,28 @@ SECRET_KEY=env-secret-123 `; describe("getEnvironmentVariablesObject with environment variables (Stack compose)", () => { - it("resolves environment variables correctly for Stack compose", () => { - const serviceEnv = ` + it("resolves environment variables correctly for Stack compose", () => { + const serviceEnv = ` FOO=\${{environment.NODE_ENV}} BAR=\${{environment.API_URL}} BAZ=test `; - const result = getEnvironmentVariablesObject( - serviceEnv, - projectEnv, - environmentEnv, - ); + const result = getEnvironmentVariablesObject( + serviceEnv, + projectEnv, + environmentEnv, + ); - expect(result).toEqual({ - FOO: "development", - BAR: "https://api.dev.example.com", - BAZ: "test", - }); - }); + expect(result).toEqual({ + FOO: "development", + BAR: "https://api.dev.example.com", + BAZ: "test", + }); + }); - it("resolves both project and environment variables for Stack compose", () => { - const serviceEnv = ` + it("resolves both project and environment variables for Stack compose", () => { + const serviceEnv = ` ENVIRONMENT=\${{project.ENVIRONMENT}} NODE_ENV=\${{environment.NODE_ENV}} API_URL=\${{environment.API_URL}} @@ -45,140 +45,140 @@ DATABASE_URL=\${{project.DATABASE_URL}} SERVICE_PORT=4000 `; - const result = getEnvironmentVariablesObject( - serviceEnv, - projectEnv, - environmentEnv, - ); + const result = getEnvironmentVariablesObject( + serviceEnv, + projectEnv, + environmentEnv, + ); - expect(result).toEqual({ - ENVIRONMENT: "staging", - NODE_ENV: "development", - API_URL: "https://api.dev.example.com", - DATABASE_URL: "postgres://postgres:postgres@localhost:5432/project_db", - SERVICE_PORT: "4000", - }); - }); + expect(result).toEqual({ + ENVIRONMENT: "staging", + NODE_ENV: "development", + API_URL: "https://api.dev.example.com", + DATABASE_URL: "postgres://postgres:postgres@localhost:5432/project_db", + SERVICE_PORT: "4000", + }); + }); - it("handles multiple environment references in single value for Stack compose", () => { - const multiRefEnv = ` + it("handles multiple environment references in single value for Stack compose", () => { + const multiRefEnv = ` HOST=localhost PORT=5432 USERNAME=postgres PASSWORD=secret123 `; - const serviceEnv = ` + const serviceEnv = ` DATABASE_URL=postgresql://\${{environment.USERNAME}}:\${{environment.PASSWORD}}@\${{environment.HOST}}:\${{environment.PORT}}/mydb `; - const result = getEnvironmentVariablesObject(serviceEnv, "", multiRefEnv); + const result = getEnvironmentVariablesObject(serviceEnv, "", multiRefEnv); - expect(result).toEqual({ - DATABASE_URL: "postgresql://postgres:secret123@localhost:5432/mydb", - }); - }); + expect(result).toEqual({ + DATABASE_URL: "postgresql://postgres:secret123@localhost:5432/mydb", + }); + }); - it("throws error for undefined environment variables in Stack compose", () => { - const serviceWithUndefined = ` + it("throws error for undefined environment variables in Stack compose", () => { + const serviceWithUndefined = ` UNDEFINED_VAR=\${{environment.UNDEFINED_VAR}} `; - expect(() => - getEnvironmentVariablesObject(serviceWithUndefined, "", environmentEnv), - ).toThrow("Invalid environment variable: environment.UNDEFINED_VAR"); - }); + expect(() => + getEnvironmentVariablesObject(serviceWithUndefined, "", environmentEnv), + ).toThrow("Invalid environment variable: environment.UNDEFINED_VAR"); + }); - it("allows service variables to override environment variables in Stack compose", () => { - const serviceOverrideEnv = ` + it("allows service variables to override environment variables in Stack compose", () => { + const serviceOverrideEnv = ` NODE_ENV=production API_URL=\${{environment.API_URL}} `; - const result = getEnvironmentVariablesObject( - serviceOverrideEnv, - "", - environmentEnv, - ); + const result = getEnvironmentVariablesObject( + serviceOverrideEnv, + "", + environmentEnv, + ); - expect(result).toEqual({ - NODE_ENV: "production", - API_URL: "https://api.dev.example.com", - }); - }); + expect(result).toEqual({ + NODE_ENV: "production", + API_URL: "https://api.dev.example.com", + }); + }); - it("resolves complex references with project, environment, and service variables for Stack compose", () => { - const complexServiceEnv = ` + it("resolves complex references with project, environment, and service variables for Stack compose", () => { + const complexServiceEnv = ` FULL_DATABASE_URL=\${{project.DATABASE_URL}}/\${{environment.DATABASE_NAME}} API_ENDPOINT=\${{environment.API_URL}}/\${{project.ENVIRONMENT}}/api SERVICE_NAME=my-service COMPLEX_VAR=\${{SERVICE_NAME}}-\${{environment.NODE_ENV}}-\${{project.ENVIRONMENT}} `; - const result = getEnvironmentVariablesObject( - complexServiceEnv, - projectEnv, - environmentEnv, - ); + const result = getEnvironmentVariablesObject( + complexServiceEnv, + projectEnv, + environmentEnv, + ); - expect(result).toEqual({ - FULL_DATABASE_URL: - "postgres://postgres:postgres@localhost:5432/project_db/dev_database", - API_ENDPOINT: "https://api.dev.example.com/staging/api", - SERVICE_NAME: "my-service", - COMPLEX_VAR: "my-service-development-staging", - }); - }); + expect(result).toEqual({ + FULL_DATABASE_URL: + "postgres://postgres:postgres@localhost:5432/project_db/dev_database", + API_ENDPOINT: "https://api.dev.example.com/staging/api", + SERVICE_NAME: "my-service", + COMPLEX_VAR: "my-service-development-staging", + }); + }); - it("maintains precedence: service > environment > project in Stack compose", () => { - const conflictingProjectEnv = ` + it("maintains precedence: service > environment > project in Stack compose", () => { + const conflictingProjectEnv = ` NODE_ENV=production-project API_URL=https://project.api.com DATABASE_NAME=project_db `; - const conflictingEnvironmentEnv = ` + const conflictingEnvironmentEnv = ` NODE_ENV=development-environment API_URL=https://environment.api.com DATABASE_NAME=env_db `; - const serviceWithConflicts = ` + const serviceWithConflicts = ` NODE_ENV=service-override PROJECT_ENV=\${{project.NODE_ENV}} ENV_VAR=\${{environment.API_URL}} DB_NAME=\${{environment.DATABASE_NAME}} `; - const result = getEnvironmentVariablesObject( - serviceWithConflicts, - conflictingProjectEnv, - conflictingEnvironmentEnv, - ); + const result = getEnvironmentVariablesObject( + serviceWithConflicts, + conflictingProjectEnv, + conflictingEnvironmentEnv, + ); - expect(result).toEqual({ - NODE_ENV: "service-override", - PROJECT_ENV: "production-project", - ENV_VAR: "https://environment.api.com", - DB_NAME: "env_db", - }); - }); + expect(result).toEqual({ + NODE_ENV: "service-override", + PROJECT_ENV: "production-project", + ENV_VAR: "https://environment.api.com", + DB_NAME: "env_db", + }); + }); - it("handles empty environment variables in Stack compose", () => { - const serviceWithEmpty = ` + it("handles empty environment variables in Stack compose", () => { + const serviceWithEmpty = ` SERVICE_VAR=test PROJECT_VAR=\${{project.ENVIRONMENT}} `; - const result = getEnvironmentVariablesObject( - serviceWithEmpty, - projectEnv, - "", - ); + const result = getEnvironmentVariablesObject( + serviceWithEmpty, + projectEnv, + "", + ); - expect(result).toEqual({ - SERVICE_VAR: "test", - PROJECT_VAR: "staging", - }); - }); + expect(result).toEqual({ + SERVICE_VAR: "test", + PROJECT_VAR: "staging", + }); + }); }); diff --git a/packages/server/src/services/user.ts b/packages/server/src/services/user.ts index 5e66c6f2c..ed43f3862 100644 --- a/packages/server/src/services/user.ts +++ b/packages/server/src/services/user.ts @@ -7,452 +7,452 @@ import { auth } from "../lib/auth"; export type User = typeof user.$inferSelect; export const addNewProject = async ( - userId: string, - projectId: string, - organizationId: string, + userId: string, + projectId: string, + organizationId: string, ) => { - const userR = await findMemberById(userId, organizationId); + const userR = await findMemberById(userId, organizationId); - await db - .update(member) - .set({ - accessedProjects: [...userR.accessedProjects, projectId], - }) - .where( - and(eq(member.id, userR.id), eq(member.organizationId, organizationId)), - ); + await db + .update(member) + .set({ + accessedProjects: [...userR.accessedProjects, projectId], + }) + .where( + and(eq(member.id, userR.id), eq(member.organizationId, organizationId)), + ); }; export const addNewEnvironment = async ( - userId: string, - environmentId: string, - organizationId: string, + userId: string, + environmentId: string, + organizationId: string, ) => { - const userR = await findMemberById(userId, organizationId); + const userR = await findMemberById(userId, organizationId); - await db - .update(member) - .set({ - accessedEnvironments: [...userR.accessedEnvironments, environmentId], - }) - .where( - and(eq(member.id, userR.id), eq(member.organizationId, organizationId)), - ); + await db + .update(member) + .set({ + accessedEnvironments: [...userR.accessedEnvironments, environmentId], + }) + .where( + and(eq(member.id, userR.id), eq(member.organizationId, organizationId)), + ); }; export const addNewService = async ( - userId: string, - serviceId: string, - organizationId: string, + userId: string, + serviceId: string, + organizationId: string, ) => { - const userR = await findMemberById(userId, organizationId); - await db - .update(member) - .set({ - accessedServices: [...userR.accessedServices, serviceId], - }) - .where( - and(eq(member.id, userR.id), eq(member.organizationId, organizationId)), - ); + const userR = await findMemberById(userId, organizationId); + await db + .update(member) + .set({ + accessedServices: [...userR.accessedServices, serviceId], + }) + .where( + and(eq(member.id, userR.id), eq(member.organizationId, organizationId)), + ); }; export const canPerformCreationService = async ( - userId: string, - projectId: string, - organizationId: string, + userId: string, + projectId: string, + organizationId: string, ) => { - const { accessedProjects, canCreateServices } = await findMemberById( - userId, - organizationId, - ); - const haveAccessToProject = accessedProjects.includes(projectId); + const { accessedProjects, canCreateServices } = await findMemberById( + userId, + organizationId, + ); + const haveAccessToProject = accessedProjects.includes(projectId); - if (canCreateServices && haveAccessToProject) { - return true; - } + if (canCreateServices && haveAccessToProject) { + return true; + } - return false; + return false; }; export const canPerformAccessService = async ( - userId: string, - serviceId: string, - organizationId: string, + userId: string, + serviceId: string, + organizationId: string, ) => { - const { accessedServices } = await findMemberById(userId, organizationId); - const haveAccessToService = accessedServices.includes(serviceId); + const { accessedServices } = await findMemberById(userId, organizationId); + const haveAccessToService = accessedServices.includes(serviceId); - if (haveAccessToService) { - return true; - } + if (haveAccessToService) { + return true; + } - return false; + return false; }; export const canPerformDeleteService = async ( - userId: string, - serviceId: string, - organizationId: string, + userId: string, + serviceId: string, + organizationId: string, ) => { - const { accessedServices, canDeleteServices } = await findMemberById( - userId, - organizationId, - ); - const haveAccessToService = accessedServices.includes(serviceId); + const { accessedServices, canDeleteServices } = await findMemberById( + userId, + organizationId, + ); + const haveAccessToService = accessedServices.includes(serviceId); - if (canDeleteServices && haveAccessToService) { - return true; - } + if (canDeleteServices && haveAccessToService) { + return true; + } - return false; + return false; }; export const canPerformCreationProject = async ( - userId: string, - organizationId: string, + userId: string, + organizationId: string, ) => { - const { canCreateProjects } = await findMemberById(userId, organizationId); + const { canCreateProjects } = await findMemberById(userId, organizationId); - if (canCreateProjects) { - return true; - } + if (canCreateProjects) { + return true; + } - return false; + return false; }; export const canPerformDeleteProject = async ( - userId: string, - organizationId: string, + userId: string, + organizationId: string, ) => { - const { canDeleteProjects } = await findMemberById(userId, organizationId); + const { canDeleteProjects } = await findMemberById(userId, organizationId); - if (canDeleteProjects) { - return true; - } + if (canDeleteProjects) { + return true; + } - return false; + return false; }; export const canPerformAccessProject = async ( - userId: string, - projectId: string, - organizationId: string, + userId: string, + projectId: string, + organizationId: string, ) => { - const { accessedProjects } = await findMemberById(userId, organizationId); + const { accessedProjects } = await findMemberById(userId, organizationId); - const haveAccessToProject = accessedProjects.includes(projectId); + const haveAccessToProject = accessedProjects.includes(projectId); - if (haveAccessToProject) { - return true; - } - return false; + if (haveAccessToProject) { + return true; + } + return false; }; export const canPerformAccessEnvironment = async ( - userId: string, - environmentId: string, - organizationId: string, + userId: string, + environmentId: string, + organizationId: string, ) => { - const { accessedEnvironments } = await findMemberById(userId, organizationId); - const haveAccessToEnvironment = accessedEnvironments.includes(environmentId); + const { accessedEnvironments } = await findMemberById(userId, organizationId); + const haveAccessToEnvironment = accessedEnvironments.includes(environmentId); - if (haveAccessToEnvironment) { - return true; - } + if (haveAccessToEnvironment) { + return true; + } - return false; + return false; }; export const canPerformDeleteEnvironment = async ( - userId: string, - projectId: string, - organizationId: string, + userId: string, + projectId: string, + organizationId: string, ) => { - const { accessedProjects, canDeleteEnvironments } = await findMemberById( - userId, - organizationId, - ); - const haveAccessToProject = accessedProjects.includes(projectId); + const { accessedProjects, canDeleteEnvironments } = await findMemberById( + userId, + organizationId, + ); + const haveAccessToProject = accessedProjects.includes(projectId); - if (canDeleteEnvironments && haveAccessToProject) { - return true; - } + if (canDeleteEnvironments && haveAccessToProject) { + return true; + } - return false; + return false; }; export const canAccessToTraefikFiles = async ( - userId: string, - organizationId: string, + userId: string, + organizationId: string, ) => { - const { canAccessToTraefikFiles } = await findMemberById( - userId, - organizationId, - ); - return canAccessToTraefikFiles; + const { canAccessToTraefikFiles } = await findMemberById( + userId, + organizationId, + ); + return canAccessToTraefikFiles; }; export const checkServiceAccess = async ( - userId: string, - serviceId: string, - organizationId: string, - action = "access" as "access" | "create" | "delete", + userId: string, + serviceId: string, + organizationId: string, + action = "access" as "access" | "create" | "delete", ) => { - let hasPermission = false; - switch (action) { - case "create": - hasPermission = await canPerformCreationService( - userId, - serviceId, - organizationId, - ); - break; - case "access": - hasPermission = await canPerformAccessService( - userId, - serviceId, - organizationId, - ); - break; - case "delete": - hasPermission = await canPerformDeleteService( - userId, - serviceId, - organizationId, - ); - break; - default: - hasPermission = false; - } - if (!hasPermission) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "Permission denied", - }); - } + let hasPermission = false; + switch (action) { + case "create": + hasPermission = await canPerformCreationService( + userId, + serviceId, + organizationId, + ); + break; + case "access": + hasPermission = await canPerformAccessService( + userId, + serviceId, + organizationId, + ); + break; + case "delete": + hasPermission = await canPerformDeleteService( + userId, + serviceId, + organizationId, + ); + break; + default: + hasPermission = false; + } + if (!hasPermission) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "Permission denied", + }); + } }; export const checkEnvironmentAccess = async ( - userId: string, - environmentId: string, - organizationId: string, - action = "access" as const, + userId: string, + environmentId: string, + organizationId: string, + action = "access" as const, ) => { - let hasPermission = false; - switch (action) { - case "access": - hasPermission = await canPerformAccessEnvironment( - userId, - environmentId, - organizationId, - ); - break; - default: - hasPermission = false; - } - if (!hasPermission) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "Permission denied", - }); - } + let hasPermission = false; + switch (action) { + case "access": + hasPermission = await canPerformAccessEnvironment( + userId, + environmentId, + organizationId, + ); + break; + default: + hasPermission = false; + } + if (!hasPermission) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "Permission denied", + }); + } }; export const checkEnvironmentDeletionPermission = async ( - userId: string, - projectId: string, - organizationId: string, + userId: string, + projectId: string, + organizationId: string, ) => { - const member = await findMemberById(userId, organizationId); + const member = await findMemberById(userId, organizationId); - if (!member) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "User not found in organization", - }); - } + if (!member) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "User not found in organization", + }); + } - if (member.role === "owner" || member.role === "admin") { - return true; - } + if (member.role === "owner" || member.role === "admin") { + return true; + } - if (!member.canDeleteEnvironments) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You don't have permission to delete environments", - }); - } + if (!member.canDeleteEnvironments) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You don't have permission to delete environments", + }); + } - const hasProjectAccess = member.accessedProjects.includes(projectId); - if (!hasProjectAccess) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You don't have access to this project", - }); - } + const hasProjectAccess = member.accessedProjects.includes(projectId); + if (!hasProjectAccess) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You don't have access to this project", + }); + } - return true; + return true; }; export const checkProjectAccess = async ( - authId: string, - action: "create" | "delete" | "access", - organizationId: string, - projectId?: string, + authId: string, + action: "create" | "delete" | "access", + organizationId: string, + projectId?: string, ) => { - let hasPermission = false; - switch (action) { - case "access": - hasPermission = await canPerformAccessProject( - authId, - projectId as string, - organizationId, - ); - break; - case "create": - hasPermission = await canPerformCreationProject(authId, organizationId); - break; - case "delete": - hasPermission = await canPerformDeleteProject(authId, organizationId); - break; - default: - hasPermission = false; - } - if (!hasPermission) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "Permission denied", - }); - } + let hasPermission = false; + switch (action) { + case "access": + hasPermission = await canPerformAccessProject( + authId, + projectId as string, + organizationId, + ); + break; + case "create": + hasPermission = await canPerformCreationProject(authId, organizationId); + break; + case "delete": + hasPermission = await canPerformDeleteProject(authId, organizationId); + break; + default: + hasPermission = false; + } + if (!hasPermission) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "Permission denied", + }); + } }; export const checkEnvironmentCreationPermission = async ( - userId: string, - projectId: string, - organizationId: string, + userId: string, + projectId: string, + organizationId: string, ) => { - // Get user's member record - const member = await findMemberById(userId, organizationId); + // Get user's member record + const member = await findMemberById(userId, organizationId); - if (!member) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "User not found in organization", - }); - } + if (!member) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "User not found in organization", + }); + } - // Owners and admins can always create environments - if (member.role === "owner" || member.role === "admin") { - return true; - } + // Owners and admins can always create environments + if (member.role === "owner" || member.role === "admin") { + return true; + } - // Check if user has canCreateEnvironments permission - if (!member.canCreateEnvironments) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You don't have permission to create environments", - }); - } + // Check if user has canCreateEnvironments permission + if (!member.canCreateEnvironments) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You don't have permission to create environments", + }); + } - // Check if user has access to the project - const hasProjectAccess = member.accessedProjects.includes(projectId); - if (!hasProjectAccess) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You don't have access to this project", - }); - } + // Check if user has access to the project + const hasProjectAccess = member.accessedProjects.includes(projectId); + if (!hasProjectAccess) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You don't have access to this project", + }); + } - return true; + return true; }; export const findMemberById = async ( - userId: string, - organizationId: string, + userId: string, + organizationId: string, ) => { - const result = await db.query.member.findFirst({ - where: and( - eq(member.userId, userId), - eq(member.organizationId, organizationId), - ), - with: { - user: true, - }, - }); + const result = await db.query.member.findFirst({ + where: and( + eq(member.userId, userId), + eq(member.organizationId, organizationId), + ), + with: { + user: true, + }, + }); - if (!result) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "Permission denied", - }); - } - return result; + if (!result) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "Permission denied", + }); + } + return result; }; 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"); - } + // 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"); - } - } + // Basic email format validation + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(userData.email)) { + throw new Error("Please enter a valid email address"); + } + } - const userResult = await db - .update(user) - .set({ - ...userData, - }) - .where(eq(user.id, userId)) - .returning() - .then((res) => res[0]); + const userResult = await db + .update(user) + .set({ + ...userData, + }) + .where(eq(user.id, userId)) + .returning() + .then((res) => res[0]); - return userResult; + return userResult; }; export const createApiKey = async ( - userId: string, - input: { - name: string; - prefix?: string; - expiresIn?: number; - metadata: { - organizationId: string; - }; - rateLimitEnabled?: boolean; - rateLimitTimeWindow?: number; - rateLimitMax?: number; - remaining?: number; - refillAmount?: number; - refillInterval?: number; - }, + userId: string, + input: { + name: string; + prefix?: string; + expiresIn?: number; + metadata: { + organizationId: string; + }; + rateLimitEnabled?: boolean; + rateLimitTimeWindow?: number; + rateLimitMax?: number; + remaining?: number; + refillAmount?: number; + refillInterval?: number; + }, ) => { - const result = await auth.createApiKey({ - body: { - name: input.name, - expiresIn: input.expiresIn, - prefix: input.prefix, - rateLimitEnabled: input.rateLimitEnabled, - rateLimitTimeWindow: input.rateLimitTimeWindow, - rateLimitMax: input.rateLimitMax, - remaining: input.remaining, - refillAmount: input.refillAmount, - refillInterval: input.refillInterval, - userId, - }, - }); + const result = await auth.createApiKey({ + body: { + name: input.name, + expiresIn: input.expiresIn, + prefix: input.prefix, + rateLimitEnabled: input.rateLimitEnabled, + rateLimitTimeWindow: input.rateLimitTimeWindow, + rateLimitMax: input.rateLimitMax, + remaining: input.remaining, + refillAmount: input.refillAmount, + refillInterval: input.refillInterval, + userId, + }, + }); - if (input.metadata) { - await db - .update(apikey) - .set({ metadata: JSON.stringify(input.metadata) }) - .where(eq(apikey.id, result.id)); - } + if (input.metadata) { + await db + .update(apikey) + .set({ metadata: JSON.stringify(input.metadata) }) + .where(eq(apikey.id, result.id)); + } - return result; + return result; }; diff --git a/packages/server/src/utils/builders/compose.ts b/packages/server/src/utils/builders/compose.ts index 32aecbd4c..316570626 100644 --- a/packages/server/src/utils/builders/compose.ts +++ b/packages/server/src/utils/builders/compose.ts @@ -5,26 +5,26 @@ import boxen from "boxen"; import { quote } from "shell-quote"; import { writeDomainsToCompose } from "../docker/domain"; import { - encodeBase64, - getEnvironmentVariablesObject, - prepareEnvironmentVariables, + encodeBase64, + getEnvironmentVariablesObject, + prepareEnvironmentVariables, } from "../docker/utils"; export type ComposeNested = InferResultType< - "compose", - { environment: { with: { project: true } }; mounts: true; domains: true } + "compose", + { environment: { with: { project: true } }; mounts: true; domains: true } >; export const getBuildComposeCommand = async (compose: ComposeNested) => { - const { COMPOSE_PATH } = paths(!!compose.serverId); - const { sourceType, appName, mounts, composeType, domains } = compose; - const command = createCommand(compose); - const envCommand = getCreateEnvFileCommand(compose); - const projectPath = join(COMPOSE_PATH, compose.appName, "code"); - const exportEnvCommand = getExportEnvCommand(compose); + const { COMPOSE_PATH } = paths(!!compose.serverId); + const { sourceType, appName, mounts, composeType, domains } = compose; + const command = createCommand(compose); + const envCommand = getCreateEnvFileCommand(compose); + const projectPath = join(COMPOSE_PATH, compose.appName, "code"); + const exportEnvCommand = getExportEnvCommand(compose); - const newCompose = await writeDomainsToCompose(compose, domains); - const logContent = ` + const newCompose = await writeDomainsToCompose(compose, domains); + const logContent = ` App Name: ${appName} Build Compose 🐳 Detected: ${mounts.length} mounts 📂 @@ -32,17 +32,17 @@ Command: docker ${command} Source Type: docker ${sourceType} ✅ Compose Type: ${composeType} ✅`; - const logBox = boxen(logContent, { - padding: { - left: 1, - right: 1, - bottom: 1, - }, - width: 80, - borderStyle: "double", - }); + const logBox = boxen(logContent, { + padding: { + left: 1, + right: 1, + bottom: 1, + }, + width: 80, + borderStyle: "double", + }); - const bashCommand = ` + const bashCommand = ` set -e { echo "${logBox}"; @@ -64,81 +64,81 @@ Compose Type: ${composeType} ✅`; } `; - return bashCommand; + return bashCommand; }; const sanitizeCommand = (command: string) => { - const sanitizedCommand = command.trim(); + const sanitizedCommand = command.trim(); - const parts = sanitizedCommand.split(/\s+/); + const parts = sanitizedCommand.split(/\s+/); - const restCommand = parts.map((arg) => arg.replace(/^"(.*)"$/, "$1")); + const restCommand = parts.map((arg) => arg.replace(/^"(.*)"$/, "$1")); - return restCommand.join(" "); + return restCommand.join(" "); }; export const createCommand = (compose: ComposeNested) => { - const { composeType, appName, sourceType } = compose; - if (compose.command) { - return `${sanitizeCommand(compose.command)}`; - } + const { composeType, appName, sourceType } = compose; + if (compose.command) { + return `${sanitizeCommand(compose.command)}`; + } - const path = - sourceType === "raw" ? "docker-compose.yml" : compose.composePath; - let command = ""; + const path = + sourceType === "raw" ? "docker-compose.yml" : compose.composePath; + let command = ""; - if (composeType === "docker-compose") { - command = `compose -p ${appName} -f ${path} up -d --build --remove-orphans`; - } else if (composeType === "stack") { - command = `stack deploy -c ${path} ${appName} --prune --with-registry-auth`; - } + if (composeType === "docker-compose") { + command = `compose -p ${appName} -f ${path} up -d --build --remove-orphans`; + } else if (composeType === "stack") { + command = `stack deploy -c ${path} ${appName} --prune --with-registry-auth`; + } - return command; + return command; }; export const getCreateEnvFileCommand = (compose: ComposeNested) => { - const { COMPOSE_PATH } = paths(!!compose.serverId); - const { env, composePath, appName } = compose; - const composeFilePath = - join(COMPOSE_PATH, appName, "code", composePath) || - join(COMPOSE_PATH, appName, "code", "docker-compose.yml"); + const { COMPOSE_PATH } = paths(!!compose.serverId); + const { env, composePath, appName } = compose; + const composeFilePath = + join(COMPOSE_PATH, appName, "code", composePath) || + join(COMPOSE_PATH, appName, "code", "docker-compose.yml"); - const envFilePath = join(dirname(composeFilePath), ".env"); + const envFilePath = join(dirname(composeFilePath), ".env"); - let envContent = `APP_NAME=${appName}\n`; - envContent += env || ""; - if (!envContent.includes("DOCKER_CONFIG")) { - envContent += "\nDOCKER_CONFIG=/root/.docker"; - } + let envContent = `APP_NAME=${appName}\n`; + envContent += env || ""; + if (!envContent.includes("DOCKER_CONFIG")) { + envContent += "\nDOCKER_CONFIG=/root/.docker"; + } - if (compose.randomize) { - envContent += `\nCOMPOSE_PREFIX=${compose.suffix}`; - } + if (compose.randomize) { + envContent += `\nCOMPOSE_PREFIX=${compose.suffix}`; + } - const envFileContent = prepareEnvironmentVariables( - envContent, - compose.environment.project.env, - compose.environment.env, - ).join("\n"); + const envFileContent = prepareEnvironmentVariables( + envContent, + compose.environment.project.env, + compose.environment.env, + ).join("\n"); - const encodedContent = encodeBase64(envFileContent); - return ` + const encodedContent = encodeBase64(envFileContent); + return ` touch ${envFilePath}; echo "${encodedContent}" | base64 -d > "${envFilePath}"; `; }; const getExportEnvCommand = (compose: ComposeNested) => { - if (compose.composeType !== "stack") return ""; + if (compose.composeType !== "stack") return ""; - const envVars = getEnvironmentVariablesObject( - compose.env, - compose.environment.project.env, - compose.environment.env, - ); - const exports = Object.entries(envVars) - .map(([key, value]) => `${key}=${quote([value])}`) - .join(" "); + const envVars = getEnvironmentVariablesObject( + compose.env, + compose.environment.project.env, + compose.environment.env, + ); + const exports = Object.entries(envVars) + .map(([key, value]) => `${key}=${quote([value])}`) + .join(" "); - return exports ? `${exports}` : ""; + return exports ? `${exports}` : ""; }; diff --git a/packages/server/src/utils/builders/docker-file.ts b/packages/server/src/utils/builders/docker-file.ts index f2fedf461..b20e3f170 100644 --- a/packages/server/src/utils/builders/docker-file.ts +++ b/packages/server/src/utils/builders/docker-file.ts @@ -1,90 +1,90 @@ import { - getEnvironmentVariablesObject, - prepareEnvironmentVariablesForShell, + getEnvironmentVariablesObject, + prepareEnvironmentVariablesForShell, } from "@dokploy/server/utils/docker/utils"; import { quote } from "shell-quote"; import { - getBuildAppDirectory, - getDockerContextPath, + getBuildAppDirectory, + getDockerContextPath, } from "../filesystem/directory"; import type { ApplicationNested } from "."; import { createEnvFileCommand } from "./utils"; export const getDockerCommand = (application: ApplicationNested) => { - const { - appName, - env, - publishDirectory, - buildArgs, - buildSecrets, - dockerBuildStage, - cleanCache, - createEnvFile, - } = application; - const dockerFilePath = getBuildAppDirectory(application); + const { + appName, + env, + publishDirectory, + buildArgs, + buildSecrets, + dockerBuildStage, + cleanCache, + createEnvFile, + } = application; + const dockerFilePath = getBuildAppDirectory(application); - try { - const image = `${appName}`; + try { + const image = `${appName}`; - const defaultContextPath = - dockerFilePath.substring(0, dockerFilePath.lastIndexOf("/") + 1) || "."; + const defaultContextPath = + dockerFilePath.substring(0, dockerFilePath.lastIndexOf("/") + 1) || "."; - const dockerContextPath = - getDockerContextPath(application) || defaultContextPath; + const dockerContextPath = + getDockerContextPath(application) || defaultContextPath; - const commandArgs = ["build", "-t", image, "-f", dockerFilePath, "."]; + const commandArgs = ["build", "-t", image, "-f", dockerFilePath, "."]; - if (dockerBuildStage) { - commandArgs.push("--target", dockerBuildStage); - } + if (dockerBuildStage) { + commandArgs.push("--target", dockerBuildStage); + } - if (cleanCache) { - commandArgs.push("--no-cache"); - } + if (cleanCache) { + commandArgs.push("--no-cache"); + } - const args = prepareEnvironmentVariablesForShell( - buildArgs, - application.environment.project.env, - application.environment.env, - ); + const args = prepareEnvironmentVariablesForShell( + buildArgs, + application.environment.project.env, + application.environment.env, + ); - for (const arg of args) { - commandArgs.push("--build-arg", arg); - } + for (const arg of args) { + commandArgs.push("--build-arg", arg); + } - const secrets = getEnvironmentVariablesObject( - buildSecrets, - application.environment.project.env, - application.environment.env, - ); + const secrets = getEnvironmentVariablesObject( + buildSecrets, + application.environment.project.env, + application.environment.env, + ); - const joinedSecrets = Object.entries(secrets) - .map(([key, value]) => `${key}=${quote([value])}`) - .join(" "); + const joinedSecrets = Object.entries(secrets) + .map(([key, value]) => `${key}=${quote([value])}`) + .join(" "); - /* + /* Do not generate an environment file when publishDirectory is specified, as it could be publicly exposed. Also respect the createEnvFile flag. */ - let command = ""; - if (!publishDirectory && createEnvFile) { - command += createEnvFileCommand( - dockerFilePath, - env, - application.environment.project.env, - application.environment.env, - ); - } + let command = ""; + if (!publishDirectory && createEnvFile) { + command += createEnvFileCommand( + dockerFilePath, + env, + application.environment.project.env, + application.environment.env, + ); + } - for (const key in secrets) { - // Although buildx is smart enough to know we may be referring to an environment variable name, - // we still make sure it doesn't fall back to `type=file`. - // See: https://docs.docker.com/reference/cli/docker/buildx/build/#secret - commandArgs.push("--secret", `type=env,id=${key}`); - } + for (const key in secrets) { + // Although buildx is smart enough to know we may be referring to an environment variable name, + // we still make sure it doesn't fall back to `type=file`. + // See: https://docs.docker.com/reference/cli/docker/buildx/build/#secret + commandArgs.push("--secret", `type=env,id=${key}`); + } - command += ` + command += ` echo "Building ${appName}" ; cd ${dockerContextPath} || { echo "❌ The path ${dockerContextPath} does not exist" ; @@ -98,8 +98,8 @@ ${joinedSecrets} docker ${commandArgs.join(" ")} || { echo "✅ Docker build completed." ; `; - return command; - } catch (error) { - throw error; - } + return command; + } catch (error) { + throw error; + } };