Skip to content

Commit e275e91

Browse files
authored
Merge pull request #3846 from Dokploy/feat/add-more-endpoints-for-search
feat: add search functionality across multiple routers with member ac…
2 parents 8d56544 + 60a6dc5 commit e275e91

File tree

9 files changed

+914
-8
lines changed

9 files changed

+914
-8
lines changed

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

Lines changed: 138 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,138 @@ 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(ilike(applications.name, `%${input.name.trim()}%`));
1055+
}
1056+
if (input.appName?.trim()) {
1057+
baseConditions.push(
1058+
ilike(applications.appName, `%${input.appName.trim()}%`),
1059+
);
1060+
}
1061+
if (input.description?.trim()) {
1062+
baseConditions.push(
1063+
ilike(
1064+
applications.description ?? "",
1065+
`%${input.description.trim()}%`,
1066+
),
1067+
);
1068+
}
1069+
if (input.repository?.trim()) {
1070+
baseConditions.push(
1071+
ilike(applications.repository ?? "", `%${input.repository.trim()}%`),
1072+
);
1073+
}
1074+
if (input.owner?.trim()) {
1075+
baseConditions.push(
1076+
ilike(applications.owner ?? "", `%${input.owner.trim()}%`),
1077+
);
1078+
}
1079+
if (input.dockerImage?.trim()) {
1080+
baseConditions.push(
1081+
ilike(
1082+
applications.dockerImage ?? "",
1083+
`%${input.dockerImage.trim()}%`,
1084+
),
1085+
);
1086+
}
1087+
1088+
if (ctx.user.role === "member") {
1089+
const { accessedServices } = await findMemberById(
1090+
ctx.user.id,
1091+
ctx.session.activeOrganizationId,
1092+
);
1093+
if (accessedServices.length === 0) return { items: [], total: 0 };
1094+
baseConditions.push(
1095+
sql`${applications.applicationId} IN (${sql.join(
1096+
accessedServices.map((id) => sql`${id}`),
1097+
sql`, `,
1098+
)})`,
1099+
);
1100+
}
1101+
1102+
const where = and(...baseConditions);
1103+
1104+
const [items, countResult] = await Promise.all([
1105+
db
1106+
.select({
1107+
applicationId: applications.applicationId,
1108+
name: applications.name,
1109+
appName: applications.appName,
1110+
description: applications.description,
1111+
environmentId: applications.environmentId,
1112+
applicationStatus: applications.applicationStatus,
1113+
sourceType: applications.sourceType,
1114+
createdAt: applications.createdAt,
1115+
})
1116+
.from(applications)
1117+
.innerJoin(
1118+
environments,
1119+
eq(applications.environmentId, environments.environmentId),
1120+
)
1121+
.innerJoin(projects, eq(environments.projectId, projects.projectId))
1122+
.where(where)
1123+
.orderBy(desc(applications.createdAt))
1124+
.limit(input.limit)
1125+
.offset(input.offset),
1126+
db
1127+
.select({ count: sql<number>`count(*)::int` })
1128+
.from(applications)
1129+
.innerJoin(
1130+
environments,
1131+
eq(applications.environmentId, environments.environmentId),
1132+
)
1133+
.innerJoin(projects, eq(environments.projectId, projects.projectId))
1134+
.where(where),
1135+
]);
1136+
1137+
return {
1138+
items,
1139+
total: countResult[0]?.count ?? 0,
1140+
};
1141+
}),
10051142
});

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

Lines changed: 114 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,114 @@ 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(ilike(composeTable.name, `%${input.name.trim()}%`));
1101+
}
1102+
if (input.appName?.trim()) {
1103+
baseConditions.push(
1104+
ilike(composeTable.appName, `%${input.appName.trim()}%`),
1105+
);
1106+
}
1107+
if (input.description?.trim()) {
1108+
baseConditions.push(
1109+
ilike(
1110+
composeTable.description ?? "",
1111+
`%${input.description.trim()}%`,
1112+
),
1113+
);
1114+
}
1115+
1116+
if (ctx.user.role === "member") {
1117+
const { accessedServices } = await findMemberById(
1118+
ctx.user.id,
1119+
ctx.session.activeOrganizationId,
1120+
);
1121+
if (accessedServices.length === 0) return { items: [], total: 0 };
1122+
baseConditions.push(
1123+
sql`${composeTable.composeId} IN (${sql.join(
1124+
accessedServices.map((id) => sql`${id}`),
1125+
sql`, `,
1126+
)})`,
1127+
);
1128+
}
1129+
1130+
const where = and(...baseConditions);
1131+
1132+
const [items, countResult] = await Promise.all([
1133+
db
1134+
.select({
1135+
composeId: composeTable.composeId,
1136+
name: composeTable.name,
1137+
appName: composeTable.appName,
1138+
description: composeTable.description,
1139+
environmentId: composeTable.environmentId,
1140+
composeStatus: composeTable.composeStatus,
1141+
sourceType: composeTable.sourceType,
1142+
createdAt: composeTable.createdAt,
1143+
})
1144+
.from(composeTable)
1145+
.innerJoin(
1146+
environments,
1147+
eq(composeTable.environmentId, environments.environmentId),
1148+
)
1149+
.innerJoin(projects, eq(environments.projectId, projects.projectId))
1150+
.where(where)
1151+
.orderBy(desc(composeTable.createdAt))
1152+
.limit(input.limit)
1153+
.offset(input.offset),
1154+
db
1155+
.select({ count: sql<number>`count(*)::int` })
1156+
.from(composeTable)
1157+
.innerJoin(
1158+
environments,
1159+
eq(composeTable.environmentId, environments.environmentId),
1160+
)
1161+
.innerJoin(projects, eq(environments.projectId, projects.projectId))
1162+
.where(where),
1163+
]);
1164+
1165+
return {
1166+
items,
1167+
total: countResult[0]?.count ?? 0,
1168+
};
1169+
}),
10571170
});

0 commit comments

Comments
 (0)