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.
This commit is contained in:
Mauricio Siu
2026-03-01 01:14:46 -06:00
parent 8d56544c1d
commit 705c5bc1c9
9 changed files with 915 additions and 8 deletions

View File

@@ -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<number>`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 };
}),
});