Skip to content

Commit 705c5bc

Browse files
committed
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.
1 parent 8d56544 commit 705c5bc

9 files changed

Lines changed: 915 additions & 8 deletions

File tree

apps/dokploy/server/api/routers/application.ts

Lines changed: 134 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
findApplicationById,
88
findEnvironmentById,
99
findGitProviderById,
10+
findMemberById,
1011
findProjectById,
1112
getApplicationStats,
1213
IS_CLOUD,
@@ -32,7 +33,7 @@ import {
3233
} from "@dokploy/server";
3334
import { db } from "@dokploy/server/db";
3435
import { TRPCError } from "@trpc/server";
35-
import { eq } from "drizzle-orm";
36+
import { and, desc, eq, ilike, or, sql } from "drizzle-orm";
3637
import { nanoid } from "nanoid";
3738
import { z } from "zod";
3839
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
@@ -53,6 +54,8 @@ import {
5354
apiSaveGitProvider,
5455
apiUpdateApplication,
5556
applications,
57+
environments,
58+
projects,
5659
} from "@/server/db/schema";
5760
import { deploymentWorker } from "@/server/queues/deployments-queue";
5861
import type { DeploymentJob } from "@/server/queues/queue-types";
@@ -1002,4 +1005,134 @@ export const applicationRouter = createTRPCRouter({
10021005
message: "Deployment cancellation only available in cloud version",
10031006
});
10041007
}),
1008+
1009+
search: protectedProcedure
1010+
.input(
1011+
z.object({
1012+
q: z.string().optional(),
1013+
name: z.string().optional(),
1014+
appName: z.string().optional(),
1015+
description: z.string().optional(),
1016+
repository: z.string().optional(),
1017+
owner: z.string().optional(),
1018+
dockerImage: z.string().optional(),
1019+
projectId: z.string().optional(),
1020+
environmentId: z.string().optional(),
1021+
limit: z.number().min(1).max(100).default(20),
1022+
offset: z.number().min(0).default(0),
1023+
}),
1024+
)
1025+
.query(async ({ ctx, input }) => {
1026+
const baseConditions = [
1027+
eq(projects.organizationId, ctx.session.activeOrganizationId),
1028+
];
1029+
1030+
if (input.projectId) {
1031+
baseConditions.push(eq(environments.projectId, input.projectId));
1032+
}
1033+
if (input.environmentId) {
1034+
baseConditions.push(
1035+
eq(applications.environmentId, input.environmentId),
1036+
);
1037+
}
1038+
1039+
if (input.q?.trim()) {
1040+
const term = `%${input.q.trim()}%`;
1041+
baseConditions.push(
1042+
or(
1043+
ilike(applications.name, term),
1044+
ilike(applications.appName, term),
1045+
ilike(applications.description ?? "", term),
1046+
ilike(applications.repository ?? "", term),
1047+
ilike(applications.owner ?? "", term),
1048+
ilike(applications.dockerImage ?? "", term),
1049+
)!,
1050+
);
1051+
}
1052+
1053+
if (input.name?.trim()) {
1054+
baseConditions.push(
1055+
ilike(applications.name, `%${input.name.trim()}%`),
1056+
);
1057+
}
1058+
if (input.appName?.trim()) {
1059+
baseConditions.push(
1060+
ilike(applications.appName, `%${input.appName.trim()}%`),
1061+
);
1062+
}
1063+
if (input.description?.trim()) {
1064+
baseConditions.push(
1065+
ilike(applications.description ?? "", `%${input.description.trim()}%`),
1066+
);
1067+
}
1068+
if (input.repository?.trim()) {
1069+
baseConditions.push(
1070+
ilike(applications.repository ?? "", `%${input.repository.trim()}%`),
1071+
);
1072+
}
1073+
if (input.owner?.trim()) {
1074+
baseConditions.push(
1075+
ilike(applications.owner ?? "", `%${input.owner.trim()}%`),
1076+
);
1077+
}
1078+
if (input.dockerImage?.trim()) {
1079+
baseConditions.push(
1080+
ilike(applications.dockerImage ?? "", `%${input.dockerImage.trim()}%`),
1081+
);
1082+
}
1083+
1084+
if (ctx.user.role === "member") {
1085+
const { accessedServices } = await findMemberById(
1086+
ctx.user.id,
1087+
ctx.session.activeOrganizationId,
1088+
);
1089+
if (accessedServices.length === 0) return { items: [], total: 0 };
1090+
baseConditions.push(
1091+
sql`${applications.applicationId} IN (${sql.join(
1092+
accessedServices.map((id) => sql`${id}`),
1093+
sql`, `,
1094+
)})`,
1095+
);
1096+
}
1097+
1098+
const where = and(...baseConditions);
1099+
1100+
const [items, countResult] = await Promise.all([
1101+
db
1102+
.select({
1103+
applicationId: applications.applicationId,
1104+
name: applications.name,
1105+
appName: applications.appName,
1106+
description: applications.description,
1107+
environmentId: applications.environmentId,
1108+
applicationStatus: applications.applicationStatus,
1109+
sourceType: applications.sourceType,
1110+
createdAt: applications.createdAt,
1111+
})
1112+
.from(applications)
1113+
.innerJoin(
1114+
environments,
1115+
eq(applications.environmentId, environments.environmentId),
1116+
)
1117+
.innerJoin(projects, eq(environments.projectId, projects.projectId))
1118+
.where(where)
1119+
.orderBy(desc(applications.createdAt))
1120+
.limit(input.limit)
1121+
.offset(input.offset),
1122+
db
1123+
.select({ count: sql<number>`count(*)::int` })
1124+
.from(applications)
1125+
.innerJoin(
1126+
environments,
1127+
eq(applications.environmentId, environments.environmentId),
1128+
)
1129+
.innerJoin(projects, eq(environments.projectId, projects.projectId))
1130+
.where(where),
1131+
]);
1132+
1133+
return {
1134+
items,
1135+
total: countResult[0]?.count ?? 0,
1136+
};
1137+
}),
10051138
});

apps/dokploy/server/api/routers/compose.ts

Lines changed: 113 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
findDomainsByComposeId,
1717
findEnvironmentById,
1818
findGitProviderById,
19+
findMemberById,
1920
findProjectById,
2021
findServerById,
2122
getComposeContainer,
@@ -41,7 +42,7 @@ import {
4142
} from "@dokploy/server/templates/github";
4243
import { processTemplate } from "@dokploy/server/templates/processors";
4344
import { TRPCError } from "@trpc/server";
44-
import { eq } from "drizzle-orm";
45+
import { and, desc, eq, ilike, or, sql } from "drizzle-orm";
4546
import _ from "lodash";
4647
import { nanoid } from "nanoid";
4748
import { parse } from "toml";
@@ -58,6 +59,8 @@ import {
5859
apiRedeployCompose,
5960
apiUpdateCompose,
6061
compose as composeTable,
62+
environments,
63+
projects,
6164
} from "@/server/db/schema";
6265
import { deploymentWorker } from "@/server/queues/deployments-queue";
6366
import type { DeploymentJob } from "@/server/queues/queue-types";
@@ -1054,4 +1057,113 @@ export const composeRouter = createTRPCRouter({
10541057
message: "Deployment cancellation only available in cloud version",
10551058
});
10561059
}),
1060+
1061+
search: protectedProcedure
1062+
.input(
1063+
z.object({
1064+
q: z.string().optional(),
1065+
name: z.string().optional(),
1066+
appName: z.string().optional(),
1067+
description: z.string().optional(),
1068+
projectId: z.string().optional(),
1069+
environmentId: z.string().optional(),
1070+
limit: z.number().min(1).max(100).default(20),
1071+
offset: z.number().min(0).default(0),
1072+
}),
1073+
)
1074+
.query(async ({ ctx, input }) => {
1075+
const baseConditions = [
1076+
eq(projects.organizationId, ctx.session.activeOrganizationId),
1077+
];
1078+
1079+
if (input.projectId) {
1080+
baseConditions.push(eq(environments.projectId, input.projectId));
1081+
}
1082+
if (input.environmentId) {
1083+
baseConditions.push(
1084+
eq(composeTable.environmentId, input.environmentId),
1085+
);
1086+
}
1087+
1088+
if (input.q?.trim()) {
1089+
const term = `%${input.q.trim()}%`;
1090+
baseConditions.push(
1091+
or(
1092+
ilike(composeTable.name, term),
1093+
ilike(composeTable.appName, term),
1094+
ilike(composeTable.description ?? "", term),
1095+
)!,
1096+
);
1097+
}
1098+
1099+
if (input.name?.trim()) {
1100+
baseConditions.push(
1101+
ilike(composeTable.name, `%${input.name.trim()}%`),
1102+
);
1103+
}
1104+
if (input.appName?.trim()) {
1105+
baseConditions.push(
1106+
ilike(composeTable.appName, `%${input.appName.trim()}%`),
1107+
);
1108+
}
1109+
if (input.description?.trim()) {
1110+
baseConditions.push(
1111+
ilike(composeTable.description ?? "", `%${input.description.trim()}%`),
1112+
);
1113+
}
1114+
1115+
if (ctx.user.role === "member") {
1116+
const { accessedServices } = await findMemberById(
1117+
ctx.user.id,
1118+
ctx.session.activeOrganizationId,
1119+
);
1120+
if (accessedServices.length === 0) return { items: [], total: 0 };
1121+
baseConditions.push(
1122+
sql`${composeTable.composeId} IN (${sql.join(
1123+
accessedServices.map((id) => sql`${id}`),
1124+
sql`, `,
1125+
)})`,
1126+
);
1127+
}
1128+
1129+
const where = and(...baseConditions);
1130+
1131+
const [items, countResult] = await Promise.all([
1132+
db
1133+
.select({
1134+
composeId: composeTable.composeId,
1135+
name: composeTable.name,
1136+
appName: composeTable.appName,
1137+
description: composeTable.description,
1138+
environmentId: composeTable.environmentId,
1139+
composeStatus: composeTable.composeStatus,
1140+
sourceType: composeTable.sourceType,
1141+
createdAt: composeTable.createdAt,
1142+
})
1143+
.from(composeTable)
1144+
.innerJoin(
1145+
environments,
1146+
eq(composeTable.environmentId, environments.environmentId),
1147+
)
1148+
.innerJoin(projects, eq(environments.projectId, projects.projectId))
1149+
.where(where)
1150+
.orderBy(desc(composeTable.createdAt))
1151+
.limit(input.limit)
1152+
.offset(input.offset),
1153+
db
1154+
.select({ count: sql<number>`count(*)::int` })
1155+
.from(composeTable)
1156+
.innerJoin(
1157+
environments,
1158+
eq(composeTable.environmentId, environments.environmentId),
1159+
)
1160+
.innerJoin(projects, eq(environments.projectId, projects.projectId))
1161+
.where(where),
1162+
]);
1163+
1164+
return {
1165+
items,
1166+
total: countResult[0]?.count ?? 0,
1167+
};
1168+
}),
10571169
});

0 commit comments

Comments
 (0)