From 705c5bc1c9baaebfcceb55ba7c7c0196bb9406a1 Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Sun, 1 Mar 2026 01:14:46 -0600 Subject: [PATCH 1/2] feat: add search functionality across multiple routers with member access control Implemented a search feature in application, compose, environment, mariadb, mongo, mysql, postgres, project, and redis routers. Each search allows filtering by various parameters and respects user permissions based on their role. The search queries utilize optimized conditions for efficient data retrieval. --- .../dokploy/server/api/routers/application.ts | 135 +++++++++++++++++- apps/dokploy/server/api/routers/compose.ts | 114 ++++++++++++++- .../dokploy/server/api/routers/environment.ts | 91 ++++++++++++ apps/dokploy/server/api/routers/mariadb.ts | 101 ++++++++++++- apps/dokploy/server/api/routers/mongo.ts | 101 ++++++++++++- apps/dokploy/server/api/routers/mysql.ts | 100 ++++++++++++- apps/dokploy/server/api/routers/postgres.ts | 101 ++++++++++++- apps/dokploy/server/api/routers/project.ts | 79 +++++++++- apps/dokploy/server/api/routers/redis.ts | 101 ++++++++++++- 9 files changed, 915 insertions(+), 8 deletions(-) diff --git a/apps/dokploy/server/api/routers/application.ts b/apps/dokploy/server/api/routers/application.ts index 97dba570e..3b61fd826 100644 --- a/apps/dokploy/server/api/routers/application.ts +++ b/apps/dokploy/server/api/routers/application.ts @@ -7,6 +7,7 @@ import { findApplicationById, findEnvironmentById, findGitProviderById, + findMemberById, findProjectById, getApplicationStats, IS_CLOUD, @@ -32,7 +33,7 @@ import { } from "@dokploy/server"; import { db } from "@dokploy/server/db"; import { TRPCError } from "@trpc/server"; -import { eq } from "drizzle-orm"; +import { and, desc, eq, ilike, or, sql } from "drizzle-orm"; import { nanoid } from "nanoid"; import { z } from "zod"; import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc"; @@ -53,6 +54,8 @@ import { apiSaveGitProvider, apiUpdateApplication, applications, + environments, + projects, } from "@/server/db/schema"; import { deploymentWorker } from "@/server/queues/deployments-queue"; import type { DeploymentJob } from "@/server/queues/queue-types"; @@ -1002,4 +1005,134 @@ export const applicationRouter = createTRPCRouter({ message: "Deployment cancellation only available in cloud version", }); }), + + search: protectedProcedure + .input( + z.object({ + q: z.string().optional(), + name: z.string().optional(), + appName: z.string().optional(), + description: z.string().optional(), + repository: z.string().optional(), + owner: z.string().optional(), + dockerImage: z.string().optional(), + projectId: z.string().optional(), + environmentId: z.string().optional(), + limit: z.number().min(1).max(100).default(20), + offset: z.number().min(0).default(0), + }), + ) + .query(async ({ ctx, input }) => { + const baseConditions = [ + eq(projects.organizationId, ctx.session.activeOrganizationId), + ]; + + if (input.projectId) { + baseConditions.push(eq(environments.projectId, input.projectId)); + } + if (input.environmentId) { + baseConditions.push( + eq(applications.environmentId, input.environmentId), + ); + } + + if (input.q?.trim()) { + const term = `%${input.q.trim()}%`; + baseConditions.push( + or( + ilike(applications.name, term), + ilike(applications.appName, term), + ilike(applications.description ?? "", term), + ilike(applications.repository ?? "", term), + ilike(applications.owner ?? "", term), + ilike(applications.dockerImage ?? "", term), + )!, + ); + } + + if (input.name?.trim()) { + baseConditions.push( + ilike(applications.name, `%${input.name.trim()}%`), + ); + } + if (input.appName?.trim()) { + baseConditions.push( + ilike(applications.appName, `%${input.appName.trim()}%`), + ); + } + if (input.description?.trim()) { + baseConditions.push( + ilike(applications.description ?? "", `%${input.description.trim()}%`), + ); + } + if (input.repository?.trim()) { + baseConditions.push( + ilike(applications.repository ?? "", `%${input.repository.trim()}%`), + ); + } + if (input.owner?.trim()) { + baseConditions.push( + ilike(applications.owner ?? "", `%${input.owner.trim()}%`), + ); + } + if (input.dockerImage?.trim()) { + baseConditions.push( + ilike(applications.dockerImage ?? "", `%${input.dockerImage.trim()}%`), + ); + } + + if (ctx.user.role === "member") { + const { accessedServices } = await findMemberById( + ctx.user.id, + ctx.session.activeOrganizationId, + ); + if (accessedServices.length === 0) return { items: [], total: 0 }; + baseConditions.push( + sql`${applications.applicationId} IN (${sql.join( + accessedServices.map((id) => sql`${id}`), + sql`, `, + )})`, + ); + } + + const where = and(...baseConditions); + + const [items, countResult] = await Promise.all([ + db + .select({ + applicationId: applications.applicationId, + name: applications.name, + appName: applications.appName, + description: applications.description, + environmentId: applications.environmentId, + applicationStatus: applications.applicationStatus, + sourceType: applications.sourceType, + createdAt: applications.createdAt, + }) + .from(applications) + .innerJoin( + environments, + eq(applications.environmentId, environments.environmentId), + ) + .innerJoin(projects, eq(environments.projectId, projects.projectId)) + .where(where) + .orderBy(desc(applications.createdAt)) + .limit(input.limit) + .offset(input.offset), + db + .select({ count: sql`count(*)::int` }) + .from(applications) + .innerJoin( + environments, + eq(applications.environmentId, environments.environmentId), + ) + .innerJoin(projects, eq(environments.projectId, projects.projectId)) + .where(where), + ]); + + return { + items, + total: countResult[0]?.count ?? 0, + }; + }), }); diff --git a/apps/dokploy/server/api/routers/compose.ts b/apps/dokploy/server/api/routers/compose.ts index f868e2ae1..390a0696c 100644 --- a/apps/dokploy/server/api/routers/compose.ts +++ b/apps/dokploy/server/api/routers/compose.ts @@ -16,6 +16,7 @@ import { findDomainsByComposeId, findEnvironmentById, findGitProviderById, + findMemberById, findProjectById, findServerById, getComposeContainer, @@ -41,7 +42,7 @@ import { } from "@dokploy/server/templates/github"; import { processTemplate } from "@dokploy/server/templates/processors"; import { TRPCError } from "@trpc/server"; -import { eq } from "drizzle-orm"; +import { and, desc, eq, ilike, or, sql } from "drizzle-orm"; import _ from "lodash"; import { nanoid } from "nanoid"; import { parse } from "toml"; @@ -58,6 +59,8 @@ import { apiRedeployCompose, apiUpdateCompose, compose as composeTable, + environments, + projects, } from "@/server/db/schema"; import { deploymentWorker } from "@/server/queues/deployments-queue"; import type { DeploymentJob } from "@/server/queues/queue-types"; @@ -1054,4 +1057,113 @@ export const composeRouter = createTRPCRouter({ message: "Deployment cancellation only available in cloud version", }); }), + + search: protectedProcedure + .input( + z.object({ + q: z.string().optional(), + name: z.string().optional(), + appName: z.string().optional(), + description: z.string().optional(), + projectId: z.string().optional(), + environmentId: z.string().optional(), + limit: z.number().min(1).max(100).default(20), + offset: z.number().min(0).default(0), + }), + ) + .query(async ({ ctx, input }) => { + const baseConditions = [ + eq(projects.organizationId, ctx.session.activeOrganizationId), + ]; + + if (input.projectId) { + baseConditions.push(eq(environments.projectId, input.projectId)); + } + if (input.environmentId) { + baseConditions.push( + eq(composeTable.environmentId, input.environmentId), + ); + } + + if (input.q?.trim()) { + const term = `%${input.q.trim()}%`; + baseConditions.push( + or( + ilike(composeTable.name, term), + ilike(composeTable.appName, term), + ilike(composeTable.description ?? "", term), + )!, + ); + } + + if (input.name?.trim()) { + baseConditions.push( + ilike(composeTable.name, `%${input.name.trim()}%`), + ); + } + if (input.appName?.trim()) { + baseConditions.push( + ilike(composeTable.appName, `%${input.appName.trim()}%`), + ); + } + if (input.description?.trim()) { + baseConditions.push( + ilike(composeTable.description ?? "", `%${input.description.trim()}%`), + ); + } + + if (ctx.user.role === "member") { + const { accessedServices } = await findMemberById( + ctx.user.id, + ctx.session.activeOrganizationId, + ); + if (accessedServices.length === 0) return { items: [], total: 0 }; + baseConditions.push( + sql`${composeTable.composeId} IN (${sql.join( + accessedServices.map((id) => sql`${id}`), + sql`, `, + )})`, + ); + } + + const where = and(...baseConditions); + + const [items, countResult] = await Promise.all([ + db + .select({ + composeId: composeTable.composeId, + name: composeTable.name, + appName: composeTable.appName, + description: composeTable.description, + environmentId: composeTable.environmentId, + composeStatus: composeTable.composeStatus, + sourceType: composeTable.sourceType, + createdAt: composeTable.createdAt, + }) + .from(composeTable) + .innerJoin( + environments, + eq(composeTable.environmentId, environments.environmentId), + ) + .innerJoin(projects, eq(environments.projectId, projects.projectId)) + .where(where) + .orderBy(desc(composeTable.createdAt)) + .limit(input.limit) + .offset(input.offset), + db + .select({ count: sql`count(*)::int` }) + .from(composeTable) + .innerJoin( + environments, + eq(composeTable.environmentId, environments.environmentId), + ) + .innerJoin(projects, eq(environments.projectId, projects.projectId)) + .where(where), + ]); + + return { + items, + total: countResult[0]?.count ?? 0, + }; + }), }); diff --git a/apps/dokploy/server/api/routers/environment.ts b/apps/dokploy/server/api/routers/environment.ts index 9f5eb45c2..173d3bd6c 100644 --- a/apps/dokploy/server/api/routers/environment.ts +++ b/apps/dokploy/server/api/routers/environment.ts @@ -11,7 +11,9 @@ import { findMemberById, updateEnvironmentById, } from "@dokploy/server"; +import { db } from "@dokploy/server/db"; import { TRPCError } from "@trpc/server"; +import { and, desc, eq, ilike, or, sql } from "drizzle-orm"; import { z } from "zod"; import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc"; import { @@ -21,6 +23,7 @@ import { apiRemoveEnvironment, apiUpdateEnvironment, } from "@/server/db/schema"; +import { environments, projects } from "@/server/db/schema"; // Helper function to filter services within an environment based on user permissions const filterEnvironmentServices = ( @@ -358,4 +361,92 @@ export const environmentRouter = createTRPCRouter({ }); } }), + + search: protectedProcedure + .input( + z.object({ + q: z.string().optional(), + name: z.string().optional(), + description: z.string().optional(), + projectId: z.string().optional(), + limit: z.number().min(1).max(100).default(20), + offset: z.number().min(0).default(0), + }), + ) + .query(async ({ ctx, input }) => { + const baseConditions = [ + eq(projects.organizationId, ctx.session.activeOrganizationId), + ]; + + if (input.projectId) { + baseConditions.push(eq(environments.projectId, input.projectId)); + } + + if (input.q?.trim()) { + const term = `%${input.q.trim()}%`; + baseConditions.push( + or( + ilike(environments.name, term), + ilike(environments.description ?? "", term), + )!, + ); + } + + if (input.name?.trim()) { + baseConditions.push( + ilike(environments.name, `%${input.name.trim()}%`), + ); + } + if (input.description?.trim()) { + baseConditions.push( + ilike(environments.description ?? "", `%${input.description.trim()}%`), + ); + } + + if (ctx.user.role === "member") { + const { accessedEnvironments } = await findMemberById( + ctx.user.id, + ctx.session.activeOrganizationId, + ); + if (accessedEnvironments.length === 0) + return { items: [], total: 0 }; + baseConditions.push( + sql`${environments.environmentId} IN (${sql.join( + accessedEnvironments.map((id) => sql`${id}`), + sql`, `, + )})`, + ); + } + + const where = and(...baseConditions); + + const [items, countResult] = await Promise.all([ + db + .select({ + environmentId: environments.environmentId, + name: environments.name, + description: environments.description, + createdAt: environments.createdAt, + env: environments.env, + projectId: environments.projectId, + isDefault: environments.isDefault, + }) + .from(environments) + .innerJoin(projects, eq(environments.projectId, projects.projectId)) + .where(where) + .orderBy(desc(environments.createdAt)) + .limit(input.limit) + .offset(input.offset), + db + .select({ count: sql`count(*)::int` }) + .from(environments) + .innerJoin(projects, eq(environments.projectId, projects.projectId)) + .where(where), + ]); + + return { + items, + total: countResult[0]?.count ?? 0, + }; + }), }); diff --git a/apps/dokploy/server/api/routers/mariadb.ts b/apps/dokploy/server/api/routers/mariadb.ts index bddc71b09..08a352e11 100644 --- a/apps/dokploy/server/api/routers/mariadb.ts +++ b/apps/dokploy/server/api/routers/mariadb.ts @@ -8,6 +8,7 @@ import { findBackupsByDbId, findEnvironmentById, findMariadbById, + findMemberById, findProjectById, IS_CLOUD, rebuildDatabase, @@ -22,7 +23,7 @@ import { import { db } from "@dokploy/server/db"; import { TRPCError } from "@trpc/server"; import { observable } from "@trpc/server/observable"; -import { eq } from "drizzle-orm"; +import { and, desc, eq, ilike, or, sql } from "drizzle-orm"; import { z } from "zod"; import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc"; import { @@ -37,6 +38,7 @@ import { apiUpdateMariaDB, mariadb as mariadbTable, } from "@/server/db/schema"; +import { environments, projects } from "@/server/db/schema"; import { cancelJobs } from "@/server/utils/backup"; export const mariadbRouter = createTRPCRouter({ create: protectedProcedure @@ -446,4 +448,101 @@ export const mariadbRouter = createTRPCRouter({ await rebuildDatabase(mariadb.mariadbId, "mariadb"); return true; }), + search: protectedProcedure + .input( + z.object({ + q: z.string().optional(), + name: z.string().optional(), + appName: z.string().optional(), + description: z.string().optional(), + projectId: z.string().optional(), + environmentId: z.string().optional(), + limit: z.number().min(1).max(100).default(20), + offset: z.number().min(0).default(0), + }), + ) + .query(async ({ ctx, input }) => { + const baseConditions = [ + eq(projects.organizationId, ctx.session.activeOrganizationId), + ]; + if (input.projectId) { + baseConditions.push(eq(environments.projectId, input.projectId)); + } + if (input.environmentId) { + baseConditions.push( + eq(mariadbTable.environmentId, input.environmentId), + ); + } + if (input.q?.trim()) { + const term = `%${input.q.trim()}%`; + baseConditions.push( + or( + ilike(mariadbTable.name, term), + ilike(mariadbTable.appName, term), + ilike(mariadbTable.description ?? "", term), + )!, + ); + } + if (input.name?.trim()) { + baseConditions.push( + ilike(mariadbTable.name, `%${input.name.trim()}%`), + ); + } + if (input.appName?.trim()) { + baseConditions.push( + ilike(mariadbTable.appName, `%${input.appName.trim()}%`), + ); + } + if (input.description?.trim()) { + baseConditions.push( + ilike(mariadbTable.description ?? "", `%${input.description.trim()}%`), + ); + } + if (ctx.user.role === "member") { + const { accessedServices } = await findMemberById( + ctx.user.id, + ctx.session.activeOrganizationId, + ); + if (accessedServices.length === 0) return { items: [], total: 0 }; + baseConditions.push( + sql`${mariadbTable.mariadbId} IN (${sql.join( + accessedServices.map((id) => sql`${id}`), + sql`, `, + )})`, + ); + } + const where = and(...baseConditions); + const [items, countResult] = await Promise.all([ + db + .select({ + mariadbId: mariadbTable.mariadbId, + name: mariadbTable.name, + appName: mariadbTable.appName, + description: mariadbTable.description, + environmentId: mariadbTable.environmentId, + applicationStatus: mariadbTable.applicationStatus, + createdAt: mariadbTable.createdAt, + }) + .from(mariadbTable) + .innerJoin( + environments, + eq(mariadbTable.environmentId, environments.environmentId), + ) + .innerJoin(projects, eq(environments.projectId, projects.projectId)) + .where(where) + .orderBy(desc(mariadbTable.createdAt)) + .limit(input.limit) + .offset(input.offset), + db + .select({ count: sql`count(*)::int` }) + .from(mariadbTable) + .innerJoin( + environments, + eq(mariadbTable.environmentId, environments.environmentId), + ) + .innerJoin(projects, eq(environments.projectId, projects.projectId)) + .where(where), + ]); + return { items, total: countResult[0]?.count ?? 0 }; + }), }); diff --git a/apps/dokploy/server/api/routers/mongo.ts b/apps/dokploy/server/api/routers/mongo.ts index e8454c8a4..c1a4a1fbd 100644 --- a/apps/dokploy/server/api/routers/mongo.ts +++ b/apps/dokploy/server/api/routers/mongo.ts @@ -7,6 +7,7 @@ import { deployMongo, findBackupsByDbId, findEnvironmentById, + findMemberById, findMongoById, findProjectById, IS_CLOUD, @@ -21,7 +22,7 @@ import { } from "@dokploy/server"; import { db } from "@dokploy/server/db"; import { TRPCError } from "@trpc/server"; -import { eq } from "drizzle-orm"; +import { and, desc, eq, ilike, or, sql } from "drizzle-orm"; import { z } from "zod"; import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc"; import { @@ -36,6 +37,7 @@ import { apiUpdateMongo, mongo as mongoTable, } from "@/server/db/schema"; +import { environments, projects } from "@/server/db/schema"; import { cancelJobs } from "@/server/utils/backup"; export const mongoRouter = createTRPCRouter({ create: protectedProcedure @@ -476,4 +478,101 @@ export const mongoRouter = createTRPCRouter({ return true; }), + search: protectedProcedure + .input( + z.object({ + q: z.string().optional(), + name: z.string().optional(), + appName: z.string().optional(), + description: z.string().optional(), + projectId: z.string().optional(), + environmentId: z.string().optional(), + limit: z.number().min(1).max(100).default(20), + offset: z.number().min(0).default(0), + }), + ) + .query(async ({ ctx, input }) => { + const baseConditions = [ + eq(projects.organizationId, ctx.session.activeOrganizationId), + ]; + if (input.projectId) { + baseConditions.push(eq(environments.projectId, input.projectId)); + } + if (input.environmentId) { + baseConditions.push( + eq(mongoTable.environmentId, input.environmentId), + ); + } + if (input.q?.trim()) { + const term = `%${input.q.trim()}%`; + baseConditions.push( + or( + ilike(mongoTable.name, term), + ilike(mongoTable.appName, term), + ilike(mongoTable.description ?? "", term), + )!, + ); + } + if (input.name?.trim()) { + baseConditions.push( + ilike(mongoTable.name, `%${input.name.trim()}%`), + ); + } + if (input.appName?.trim()) { + baseConditions.push( + ilike(mongoTable.appName, `%${input.appName.trim()}%`), + ); + } + if (input.description?.trim()) { + baseConditions.push( + ilike(mongoTable.description ?? "", `%${input.description.trim()}%`), + ); + } + if (ctx.user.role === "member") { + const { accessedServices } = await findMemberById( + ctx.user.id, + ctx.session.activeOrganizationId, + ); + if (accessedServices.length === 0) return { items: [], total: 0 }; + baseConditions.push( + sql`${mongoTable.mongoId} IN (${sql.join( + accessedServices.map((id) => sql`${id}`), + sql`, `, + )})`, + ); + } + const where = and(...baseConditions); + const [items, countResult] = await Promise.all([ + db + .select({ + mongoId: mongoTable.mongoId, + name: mongoTable.name, + appName: mongoTable.appName, + description: mongoTable.description, + environmentId: mongoTable.environmentId, + applicationStatus: mongoTable.applicationStatus, + createdAt: mongoTable.createdAt, + }) + .from(mongoTable) + .innerJoin( + environments, + eq(mongoTable.environmentId, environments.environmentId), + ) + .innerJoin(projects, eq(environments.projectId, projects.projectId)) + .where(where) + .orderBy(desc(mongoTable.createdAt)) + .limit(input.limit) + .offset(input.offset), + db + .select({ count: sql`count(*)::int` }) + .from(mongoTable) + .innerJoin( + environments, + eq(mongoTable.environmentId, environments.environmentId), + ) + .innerJoin(projects, eq(environments.projectId, projects.projectId)) + .where(where), + ]); + return { items, total: countResult[0]?.count ?? 0 }; + }), }); diff --git a/apps/dokploy/server/api/routers/mysql.ts b/apps/dokploy/server/api/routers/mysql.ts index b1bc10f32..4ffaa9611 100644 --- a/apps/dokploy/server/api/routers/mysql.ts +++ b/apps/dokploy/server/api/routers/mysql.ts @@ -7,6 +7,7 @@ import { deployMySql, findBackupsByDbId, findEnvironmentById, + findMemberById, findMySqlById, findProjectById, IS_CLOUD, @@ -21,7 +22,7 @@ import { } from "@dokploy/server"; import { db } from "@dokploy/server/db"; import { TRPCError } from "@trpc/server"; -import { eq } from "drizzle-orm"; +import { and, desc, eq, ilike, or, sql } from "drizzle-orm"; import { z } from "zod"; import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc"; import { @@ -34,7 +35,9 @@ import { apiSaveEnvironmentVariablesMySql, apiSaveExternalPortMySql, apiUpdateMySql, + environments, mysql as mysqlTable, + projects, } from "@/server/db/schema"; import { cancelJobs } from "@/server/utils/backup"; @@ -471,4 +474,99 @@ export const mysqlRouter = createTRPCRouter({ return true; }), + search: protectedProcedure + .input( + z.object({ + q: z.string().optional(), + name: z.string().optional(), + appName: z.string().optional(), + description: z.string().optional(), + projectId: z.string().optional(), + environmentId: z.string().optional(), + limit: z.number().min(1).max(100).default(20), + offset: z.number().min(0).default(0), + }), + ) + .query(async ({ ctx, input }) => { + const baseConditions = [ + eq(projects.organizationId, ctx.session.activeOrganizationId), + ]; + if (input.projectId) { + baseConditions.push(eq(environments.projectId, input.projectId)); + } + if (input.environmentId) { + baseConditions.push(eq(mysqlTable.environmentId, input.environmentId)); + } + if (input.q?.trim()) { + const term = `%${input.q.trim()}%`; + baseConditions.push( + or( + ilike(mysqlTable.name, term), + ilike(mysqlTable.appName, term), + ilike(mysqlTable.description ?? "", term), + )!, + ); + } + if (input.name?.trim()) { + baseConditions.push( + ilike(mysqlTable.name, `%${input.name.trim()}%`), + ); + } + if (input.appName?.trim()) { + baseConditions.push( + ilike(mysqlTable.appName, `%${input.appName.trim()}%`), + ); + } + if (input.description?.trim()) { + baseConditions.push( + ilike(mysqlTable.description ?? "", `%${input.description.trim()}%`), + ); + } + if (ctx.user.role === "member") { + const { accessedServices } = await findMemberById( + ctx.user.id, + ctx.session.activeOrganizationId, + ); + if (accessedServices.length === 0) return { items: [], total: 0 }; + baseConditions.push( + sql`${mysqlTable.mysqlId} IN (${sql.join( + accessedServices.map((id) => sql`${id}`), + sql`, `, + )})`, + ); + } + const where = and(...baseConditions); + const [items, countResult] = await Promise.all([ + db + .select({ + mysqlId: mysqlTable.mysqlId, + name: mysqlTable.name, + appName: mysqlTable.appName, + description: mysqlTable.description, + environmentId: mysqlTable.environmentId, + applicationStatus: mysqlTable.applicationStatus, + createdAt: mysqlTable.createdAt, + }) + .from(mysqlTable) + .innerJoin( + environments, + eq(mysqlTable.environmentId, environments.environmentId), + ) + .innerJoin(projects, eq(environments.projectId, projects.projectId)) + .where(where) + .orderBy(desc(mysqlTable.createdAt)) + .limit(input.limit) + .offset(input.offset), + db + .select({ count: sql`count(*)::int` }) + .from(mysqlTable) + .innerJoin( + environments, + eq(mysqlTable.environmentId, environments.environmentId), + ) + .innerJoin(projects, eq(environments.projectId, projects.projectId)) + .where(where), + ]); + return { items, total: countResult[0]?.count ?? 0 }; + }), }); diff --git a/apps/dokploy/server/api/routers/postgres.ts b/apps/dokploy/server/api/routers/postgres.ts index d9f69330c..6701615c8 100644 --- a/apps/dokploy/server/api/routers/postgres.ts +++ b/apps/dokploy/server/api/routers/postgres.ts @@ -7,6 +7,7 @@ import { deployPostgres, findBackupsByDbId, findEnvironmentById, + findMemberById, findPostgresById, findProjectById, getMountPath, @@ -22,7 +23,7 @@ import { } from "@dokploy/server"; import { db } from "@dokploy/server/db"; import { TRPCError } from "@trpc/server"; -import { eq } from "drizzle-orm"; +import { and, desc, eq, ilike, or, sql } from "drizzle-orm"; import { z } from "zod"; import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc"; import { @@ -37,6 +38,7 @@ import { apiUpdatePostgres, postgres as postgresTable, } from "@/server/db/schema"; +import { environments, projects } from "@/server/db/schema"; import { cancelJobs } from "@/server/utils/backup"; export const postgresRouter = createTRPCRouter({ @@ -483,4 +485,101 @@ export const postgresRouter = createTRPCRouter({ return true; }), + search: protectedProcedure + .input( + z.object({ + q: z.string().optional(), + name: z.string().optional(), + appName: z.string().optional(), + description: z.string().optional(), + projectId: z.string().optional(), + environmentId: z.string().optional(), + limit: z.number().min(1).max(100).default(20), + offset: z.number().min(0).default(0), + }), + ) + .query(async ({ ctx, input }) => { + const baseConditions = [ + eq(projects.organizationId, ctx.session.activeOrganizationId), + ]; + if (input.projectId) { + baseConditions.push(eq(environments.projectId, input.projectId)); + } + if (input.environmentId) { + baseConditions.push( + eq(postgresTable.environmentId, input.environmentId), + ); + } + if (input.q?.trim()) { + const term = `%${input.q.trim()}%`; + baseConditions.push( + or( + ilike(postgresTable.name, term), + ilike(postgresTable.appName, term), + ilike(postgresTable.description ?? "", term), + )!, + ); + } + if (input.name?.trim()) { + baseConditions.push( + ilike(postgresTable.name, `%${input.name.trim()}%`), + ); + } + if (input.appName?.trim()) { + baseConditions.push( + ilike(postgresTable.appName, `%${input.appName.trim()}%`), + ); + } + if (input.description?.trim()) { + baseConditions.push( + ilike(postgresTable.description ?? "", `%${input.description.trim()}%`), + ); + } + if (ctx.user.role === "member") { + const { accessedServices } = await findMemberById( + ctx.user.id, + ctx.session.activeOrganizationId, + ); + if (accessedServices.length === 0) return { items: [], total: 0 }; + baseConditions.push( + sql`${postgresTable.postgresId} IN (${sql.join( + accessedServices.map((id) => sql`${id}`), + sql`, `, + )})`, + ); + } + const where = and(...baseConditions); + const [items, countResult] = await Promise.all([ + db + .select({ + postgresId: postgresTable.postgresId, + name: postgresTable.name, + appName: postgresTable.appName, + description: postgresTable.description, + environmentId: postgresTable.environmentId, + applicationStatus: postgresTable.applicationStatus, + createdAt: postgresTable.createdAt, + }) + .from(postgresTable) + .innerJoin( + environments, + eq(postgresTable.environmentId, environments.environmentId), + ) + .innerJoin(projects, eq(environments.projectId, projects.projectId)) + .where(where) + .orderBy(desc(postgresTable.createdAt)) + .limit(input.limit) + .offset(input.offset), + db + .select({ count: sql`count(*)::int` }) + .from(postgresTable) + .innerJoin( + environments, + eq(postgresTable.environmentId, environments.environmentId), + ) + .innerJoin(projects, eq(environments.projectId, projects.projectId)) + .where(where), + ]); + return { items, total: countResult[0]?.count ?? 0 }; + }), }); diff --git a/apps/dokploy/server/api/routers/project.ts b/apps/dokploy/server/api/routers/project.ts index b1df68951..ee353594c 100644 --- a/apps/dokploy/server/api/routers/project.ts +++ b/apps/dokploy/server/api/routers/project.ts @@ -34,7 +34,7 @@ import { } from "@dokploy/server"; import { db } from "@dokploy/server/db"; import { TRPCError } from "@trpc/server"; -import { and, desc, eq, sql } from "drizzle-orm"; +import { and, desc, eq, ilike, or, sql } from "drizzle-orm"; import type { AnyPgColumn } from "drizzle-orm/pg-core"; import { z } from "zod"; import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc"; @@ -277,6 +277,83 @@ export const projectRouter = createTRPCRouter({ }); }), + search: protectedProcedure + .input( + z.object({ + q: z.string().optional(), + name: z.string().optional(), + description: z.string().optional(), + limit: z.number().min(1).max(100).default(20), + offset: z.number().min(0).default(0), + }), + ) + .query(async ({ ctx, input }) => { + const baseConditions = [ + eq(projects.organizationId, ctx.session.activeOrganizationId), + ]; + + if (input.q?.trim()) { + const term = `%${input.q.trim()}%`; + baseConditions.push( + or( + ilike(projects.name, term), + ilike(projects.description ?? "", term), + )!, + ); + } + + if (input.name?.trim()) { + baseConditions.push(ilike(projects.name, `%${input.name.trim()}%`)); + } + if (input.description?.trim()) { + baseConditions.push( + ilike(projects.description ?? "", `%${input.description.trim()}%`), + ); + } + + if (ctx.user.role === "member") { + const { accessedProjects } = await findMemberById( + ctx.user.id, + ctx.session.activeOrganizationId, + ); + if (accessedProjects.length === 0) return { items: [], total: 0 }; + baseConditions.push( + sql`${projects.projectId} IN (${sql.join( + accessedProjects.map((id) => sql`${id}`), + sql`, `, + )})`, + ); + } + + const where = and(...baseConditions); + + const [items, countResult] = await Promise.all([ + db.query.projects.findMany({ + where, + limit: input.limit, + offset: input.offset, + orderBy: desc(projects.createdAt), + columns: { + projectId: true, + name: true, + description: true, + createdAt: true, + organizationId: true, + env: true, + }, + }), + db + .select({ count: sql`count(*)::int` }) + .from(projects) + .where(where), + ]); + + return { + items, + total: countResult[0]?.count ?? 0, + }; + }), + remove: protectedProcedure .input(apiRemoveProject) .mutation(async ({ input, ctx }) => { diff --git a/apps/dokploy/server/api/routers/redis.ts b/apps/dokploy/server/api/routers/redis.ts index dfeff82bb..5c058276b 100644 --- a/apps/dokploy/server/api/routers/redis.ts +++ b/apps/dokploy/server/api/routers/redis.ts @@ -6,6 +6,7 @@ import { createRedis, deployRedis, findEnvironmentById, + findMemberById, findProjectById, findRedisById, IS_CLOUD, @@ -20,7 +21,7 @@ import { } from "@dokploy/server"; import { db } from "@dokploy/server/db"; import { TRPCError } from "@trpc/server"; -import { eq } from "drizzle-orm"; +import { and, desc, eq, ilike, or, sql } from "drizzle-orm"; import { z } from "zod"; import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc"; import { @@ -35,6 +36,7 @@ import { apiUpdateRedis, redis as redisTable, } from "@/server/db/schema"; +import { environments, projects } from "@/server/db/schema"; export const redisRouter = createTRPCRouter({ create: protectedProcedure .input(apiCreateRedis) @@ -450,4 +452,101 @@ export const redisRouter = createTRPCRouter({ await rebuildDatabase(redis.redisId, "redis"); return true; }), + search: protectedProcedure + .input( + z.object({ + q: z.string().optional(), + name: z.string().optional(), + appName: z.string().optional(), + description: z.string().optional(), + projectId: z.string().optional(), + environmentId: z.string().optional(), + limit: z.number().min(1).max(100).default(20), + offset: z.number().min(0).default(0), + }), + ) + .query(async ({ ctx, input }) => { + const baseConditions = [ + eq(projects.organizationId, ctx.session.activeOrganizationId), + ]; + if (input.projectId) { + baseConditions.push(eq(environments.projectId, input.projectId)); + } + if (input.environmentId) { + baseConditions.push( + eq(redisTable.environmentId, input.environmentId), + ); + } + if (input.q?.trim()) { + const term = `%${input.q.trim()}%`; + baseConditions.push( + or( + ilike(redisTable.name, term), + ilike(redisTable.appName, term), + ilike(redisTable.description ?? "", term), + )!, + ); + } + if (input.name?.trim()) { + baseConditions.push( + ilike(redisTable.name, `%${input.name.trim()}%`), + ); + } + if (input.appName?.trim()) { + baseConditions.push( + ilike(redisTable.appName, `%${input.appName.trim()}%`), + ); + } + if (input.description?.trim()) { + baseConditions.push( + ilike(redisTable.description ?? "", `%${input.description.trim()}%`), + ); + } + if (ctx.user.role === "member") { + const { accessedServices } = await findMemberById( + ctx.user.id, + ctx.session.activeOrganizationId, + ); + if (accessedServices.length === 0) return { items: [], total: 0 }; + baseConditions.push( + sql`${redisTable.redisId} IN (${sql.join( + accessedServices.map((id) => sql`${id}`), + sql`, `, + )})`, + ); + } + const where = and(...baseConditions); + const [items, countResult] = await Promise.all([ + db + .select({ + redisId: redisTable.redisId, + name: redisTable.name, + appName: redisTable.appName, + description: redisTable.description, + environmentId: redisTable.environmentId, + applicationStatus: redisTable.applicationStatus, + createdAt: redisTable.createdAt, + }) + .from(redisTable) + .innerJoin( + environments, + eq(redisTable.environmentId, environments.environmentId), + ) + .innerJoin(projects, eq(environments.projectId, projects.projectId)) + .where(where) + .orderBy(desc(redisTable.createdAt)) + .limit(input.limit) + .offset(input.offset), + db + .select({ count: sql`count(*)::int` }) + .from(redisTable) + .innerJoin( + environments, + eq(redisTable.environmentId, environments.environmentId), + ) + .innerJoin(projects, eq(environments.projectId, projects.projectId)) + .where(where), + ]); + return { items, total: countResult[0]?.count ?? 0 }; + }), }); From 60a6dc5fab9a264e6b44a7a1cf17440703ac3a39 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Sun, 1 Mar 2026 07:15:20 +0000 Subject: [PATCH 2/2] [autofix.ci] apply automated fixes --- apps/dokploy/server/api/routers/application.ts | 14 +++++++++----- apps/dokploy/server/api/routers/compose.ts | 9 +++++---- apps/dokploy/server/api/routers/environment.ts | 12 ++++++------ apps/dokploy/server/api/routers/mariadb.ts | 9 +++++---- apps/dokploy/server/api/routers/mongo.ts | 8 ++------ apps/dokploy/server/api/routers/mysql.ts | 4 +--- apps/dokploy/server/api/routers/postgres.ts | 5 ++++- apps/dokploy/server/api/routers/redis.ts | 8 ++------ 8 files changed, 34 insertions(+), 35 deletions(-) diff --git a/apps/dokploy/server/api/routers/application.ts b/apps/dokploy/server/api/routers/application.ts index 3b61fd826..df3e81c82 100644 --- a/apps/dokploy/server/api/routers/application.ts +++ b/apps/dokploy/server/api/routers/application.ts @@ -1051,9 +1051,7 @@ export const applicationRouter = createTRPCRouter({ } if (input.name?.trim()) { - baseConditions.push( - ilike(applications.name, `%${input.name.trim()}%`), - ); + baseConditions.push(ilike(applications.name, `%${input.name.trim()}%`)); } if (input.appName?.trim()) { baseConditions.push( @@ -1062,7 +1060,10 @@ export const applicationRouter = createTRPCRouter({ } if (input.description?.trim()) { baseConditions.push( - ilike(applications.description ?? "", `%${input.description.trim()}%`), + ilike( + applications.description ?? "", + `%${input.description.trim()}%`, + ), ); } if (input.repository?.trim()) { @@ -1077,7 +1078,10 @@ export const applicationRouter = createTRPCRouter({ } if (input.dockerImage?.trim()) { baseConditions.push( - ilike(applications.dockerImage ?? "", `%${input.dockerImage.trim()}%`), + ilike( + applications.dockerImage ?? "", + `%${input.dockerImage.trim()}%`, + ), ); } diff --git a/apps/dokploy/server/api/routers/compose.ts b/apps/dokploy/server/api/routers/compose.ts index 390a0696c..e3c803cd4 100644 --- a/apps/dokploy/server/api/routers/compose.ts +++ b/apps/dokploy/server/api/routers/compose.ts @@ -1097,9 +1097,7 @@ export const composeRouter = createTRPCRouter({ } if (input.name?.trim()) { - baseConditions.push( - ilike(composeTable.name, `%${input.name.trim()}%`), - ); + baseConditions.push(ilike(composeTable.name, `%${input.name.trim()}%`)); } if (input.appName?.trim()) { baseConditions.push( @@ -1108,7 +1106,10 @@ export const composeRouter = createTRPCRouter({ } if (input.description?.trim()) { baseConditions.push( - ilike(composeTable.description ?? "", `%${input.description.trim()}%`), + ilike( + composeTable.description ?? "", + `%${input.description.trim()}%`, + ), ); } diff --git a/apps/dokploy/server/api/routers/environment.ts b/apps/dokploy/server/api/routers/environment.ts index 173d3bd6c..16376e9e0 100644 --- a/apps/dokploy/server/api/routers/environment.ts +++ b/apps/dokploy/server/api/routers/environment.ts @@ -393,13 +393,14 @@ export const environmentRouter = createTRPCRouter({ } if (input.name?.trim()) { - baseConditions.push( - ilike(environments.name, `%${input.name.trim()}%`), - ); + baseConditions.push(ilike(environments.name, `%${input.name.trim()}%`)); } if (input.description?.trim()) { baseConditions.push( - ilike(environments.description ?? "", `%${input.description.trim()}%`), + ilike( + environments.description ?? "", + `%${input.description.trim()}%`, + ), ); } @@ -408,8 +409,7 @@ export const environmentRouter = createTRPCRouter({ ctx.user.id, ctx.session.activeOrganizationId, ); - if (accessedEnvironments.length === 0) - return { items: [], total: 0 }; + if (accessedEnvironments.length === 0) return { items: [], total: 0 }; baseConditions.push( sql`${environments.environmentId} IN (${sql.join( accessedEnvironments.map((id) => sql`${id}`), diff --git a/apps/dokploy/server/api/routers/mariadb.ts b/apps/dokploy/server/api/routers/mariadb.ts index 08a352e11..567cd4ad8 100644 --- a/apps/dokploy/server/api/routers/mariadb.ts +++ b/apps/dokploy/server/api/routers/mariadb.ts @@ -484,9 +484,7 @@ export const mariadbRouter = createTRPCRouter({ ); } if (input.name?.trim()) { - baseConditions.push( - ilike(mariadbTable.name, `%${input.name.trim()}%`), - ); + baseConditions.push(ilike(mariadbTable.name, `%${input.name.trim()}%`)); } if (input.appName?.trim()) { baseConditions.push( @@ -495,7 +493,10 @@ export const mariadbRouter = createTRPCRouter({ } if (input.description?.trim()) { baseConditions.push( - ilike(mariadbTable.description ?? "", `%${input.description.trim()}%`), + ilike( + mariadbTable.description ?? "", + `%${input.description.trim()}%`, + ), ); } if (ctx.user.role === "member") { diff --git a/apps/dokploy/server/api/routers/mongo.ts b/apps/dokploy/server/api/routers/mongo.ts index c1a4a1fbd..ec0a4041c 100644 --- a/apps/dokploy/server/api/routers/mongo.ts +++ b/apps/dokploy/server/api/routers/mongo.ts @@ -499,9 +499,7 @@ export const mongoRouter = createTRPCRouter({ baseConditions.push(eq(environments.projectId, input.projectId)); } if (input.environmentId) { - baseConditions.push( - eq(mongoTable.environmentId, input.environmentId), - ); + baseConditions.push(eq(mongoTable.environmentId, input.environmentId)); } if (input.q?.trim()) { const term = `%${input.q.trim()}%`; @@ -514,9 +512,7 @@ export const mongoRouter = createTRPCRouter({ ); } if (input.name?.trim()) { - baseConditions.push( - ilike(mongoTable.name, `%${input.name.trim()}%`), - ); + baseConditions.push(ilike(mongoTable.name, `%${input.name.trim()}%`)); } if (input.appName?.trim()) { baseConditions.push( diff --git a/apps/dokploy/server/api/routers/mysql.ts b/apps/dokploy/server/api/routers/mysql.ts index 4ffaa9611..5a00ef0d0 100644 --- a/apps/dokploy/server/api/routers/mysql.ts +++ b/apps/dokploy/server/api/routers/mysql.ts @@ -508,9 +508,7 @@ export const mysqlRouter = createTRPCRouter({ ); } if (input.name?.trim()) { - baseConditions.push( - ilike(mysqlTable.name, `%${input.name.trim()}%`), - ); + baseConditions.push(ilike(mysqlTable.name, `%${input.name.trim()}%`)); } if (input.appName?.trim()) { baseConditions.push( diff --git a/apps/dokploy/server/api/routers/postgres.ts b/apps/dokploy/server/api/routers/postgres.ts index 6701615c8..48de9d5a2 100644 --- a/apps/dokploy/server/api/routers/postgres.ts +++ b/apps/dokploy/server/api/routers/postgres.ts @@ -532,7 +532,10 @@ export const postgresRouter = createTRPCRouter({ } if (input.description?.trim()) { baseConditions.push( - ilike(postgresTable.description ?? "", `%${input.description.trim()}%`), + ilike( + postgresTable.description ?? "", + `%${input.description.trim()}%`, + ), ); } if (ctx.user.role === "member") { diff --git a/apps/dokploy/server/api/routers/redis.ts b/apps/dokploy/server/api/routers/redis.ts index 5c058276b..94939bd20 100644 --- a/apps/dokploy/server/api/routers/redis.ts +++ b/apps/dokploy/server/api/routers/redis.ts @@ -473,9 +473,7 @@ export const redisRouter = createTRPCRouter({ baseConditions.push(eq(environments.projectId, input.projectId)); } if (input.environmentId) { - baseConditions.push( - eq(redisTable.environmentId, input.environmentId), - ); + baseConditions.push(eq(redisTable.environmentId, input.environmentId)); } if (input.q?.trim()) { const term = `%${input.q.trim()}%`; @@ -488,9 +486,7 @@ export const redisRouter = createTRPCRouter({ ); } if (input.name?.trim()) { - baseConditions.push( - ilike(redisTable.name, `%${input.name.trim()}%`), - ); + baseConditions.push(ilike(redisTable.name, `%${input.name.trim()}%`)); } if (input.appName?.trim()) { baseConditions.push(