mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-06-18 13:45:23 +02:00
Compare commits
18 Commits
fix/breaki
...
feat/add-k
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e275e9162e | ||
|
|
60a6dc5fab | ||
|
|
705c5bc1c9 | ||
|
|
8d56544c1d | ||
|
|
ca527ab6ff | ||
|
|
439fa17292 | ||
|
|
096c04486c | ||
|
|
c9e1079076 | ||
|
|
e29a86a85f | ||
|
|
1ba0eb0c2e | ||
|
|
d7dc10993e | ||
|
|
2a5d3975e8 | ||
|
|
9f3356ddb4 | ||
|
|
f5674f5bf8 | ||
|
|
17a617e585 | ||
|
|
f50eea9e05 | ||
|
|
81ee8f653a | ||
|
|
9507745cc0 |
@@ -14,13 +14,18 @@ vi.mock("@dokploy/server/db", () => {
|
||||
set: vi.fn(() => chain),
|
||||
where: vi.fn(() => chain),
|
||||
returning: vi.fn().mockResolvedValue([{}] as any),
|
||||
from: vi.fn(() => chain),
|
||||
innerJoin: vi.fn(() => chain),
|
||||
then: (resolve: (v: any) => void) => {
|
||||
resolve([]);
|
||||
},
|
||||
} as any;
|
||||
return chain;
|
||||
};
|
||||
|
||||
return {
|
||||
db: {
|
||||
select: vi.fn(),
|
||||
select: vi.fn(() => createChainableMock()),
|
||||
insert: vi.fn(),
|
||||
update: vi.fn(() => createChainableMock()),
|
||||
delete: vi.fn(),
|
||||
@@ -31,6 +36,9 @@ vi.mock("@dokploy/server/db", () => {
|
||||
patch: {
|
||||
findMany: vi.fn().mockResolvedValue([]),
|
||||
},
|
||||
member: {
|
||||
findMany: vi.fn().mockResolvedValue([]),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -15,13 +15,18 @@ vi.mock("@dokploy/server/db", () => {
|
||||
set: vi.fn(() => chain),
|
||||
where: vi.fn(() => chain),
|
||||
returning: vi.fn().mockResolvedValue([{}]),
|
||||
from: vi.fn(() => chain),
|
||||
innerJoin: vi.fn(() => chain),
|
||||
then: (resolve: (v: any) => void) => {
|
||||
resolve([]);
|
||||
},
|
||||
};
|
||||
return chain;
|
||||
};
|
||||
|
||||
return {
|
||||
db: {
|
||||
select: vi.fn(),
|
||||
select: vi.fn(() => createChainableMock()),
|
||||
insert: vi.fn(),
|
||||
update: vi.fn(() => createChainableMock()),
|
||||
delete: vi.fn(),
|
||||
@@ -32,6 +37,9 @@ vi.mock("@dokploy/server/db", () => {
|
||||
patch: {
|
||||
findMany: vi.fn().mockResolvedValue([]),
|
||||
},
|
||||
member: {
|
||||
findMany: vi.fn().mockResolvedValue([]),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -12,7 +12,11 @@ vi.mock("@dokploy/server/db", () => {
|
||||
chain.where = () => chain;
|
||||
chain.values = () => chain;
|
||||
chain.returning = () => Promise.resolve([{}]);
|
||||
chain.then = undefined;
|
||||
chain.from = () => chain;
|
||||
chain.innerJoin = () => chain;
|
||||
chain.then = (resolve: (value: unknown) => void) => {
|
||||
resolve([]);
|
||||
};
|
||||
|
||||
const tableMock = {
|
||||
findFirst: vi.fn(() => Promise.resolve(undefined)),
|
||||
@@ -21,7 +25,6 @@ vi.mock("@dokploy/server/db", () => {
|
||||
update: vi.fn(() => chain),
|
||||
delete: vi.fn(() => chain),
|
||||
};
|
||||
const createQueryMock = () => tableMock;
|
||||
|
||||
return {
|
||||
db: {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { standardSchemaResolver as zodResolver } from "@hookform/resolvers/standard-schema";
|
||||
import { PenBoxIcon } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
@@ -41,6 +41,7 @@ interface Props {
|
||||
}
|
||||
|
||||
export const UpdateMariadb = ({ mariadbId }: Props) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const utils = api.useUtils();
|
||||
const { mutateAsync, error, isError, isPending } =
|
||||
api.mariadb.update.useMutation();
|
||||
@@ -79,6 +80,7 @@ export const UpdateMariadb = ({ mariadbId }: Props) => {
|
||||
utils.mariadb.one.invalidate({
|
||||
mariadbId: mariadbId,
|
||||
});
|
||||
setIsOpen(false);
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error updating the Mariadb");
|
||||
@@ -87,7 +89,7 @@ export const UpdateMariadb = ({ mariadbId }: Props) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog>
|
||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { standardSchemaResolver as zodResolver } from "@hookform/resolvers/standard-schema";
|
||||
import { PenBoxIcon } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
@@ -41,6 +41,7 @@ interface Props {
|
||||
}
|
||||
|
||||
export const UpdateMysql = ({ mysqlId }: Props) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const utils = api.useUtils();
|
||||
const { mutateAsync, error, isError, isPending } =
|
||||
api.mysql.update.useMutation();
|
||||
@@ -79,6 +80,7 @@ export const UpdateMysql = ({ mysqlId }: Props) => {
|
||||
utils.mysql.one.invalidate({
|
||||
mysqlId: mysqlId,
|
||||
});
|
||||
setIsOpen(false);
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error updating MySQL");
|
||||
@@ -87,7 +89,7 @@ export const UpdateMysql = ({ mysqlId }: Props) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog>
|
||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { standardSchemaResolver as zodResolver } from "@hookform/resolvers/standard-schema";
|
||||
import { PenBoxIcon } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
@@ -41,6 +41,7 @@ interface Props {
|
||||
}
|
||||
|
||||
export const UpdateRedis = ({ redisId }: Props) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const utils = api.useUtils();
|
||||
const { mutateAsync, error, isError, isPending } =
|
||||
api.redis.update.useMutation();
|
||||
@@ -79,6 +80,7 @@ export const UpdateRedis = ({ redisId }: Props) => {
|
||||
utils.redis.one.invalidate({
|
||||
redisId: redisId,
|
||||
});
|
||||
setIsOpen(false);
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error updating Redis");
|
||||
@@ -87,7 +89,7 @@ export const UpdateRedis = ({ redisId }: Props) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog>
|
||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
|
||||
@@ -30,7 +30,7 @@ export const AddGithubProvider = () => {
|
||||
const url = document.location.origin;
|
||||
const manifest = JSON.stringify(
|
||||
{
|
||||
redirect_url: `${origin}/api/providers/github/setup?organizationId=${activeOrganization?.id}&userId=${session?.user?.id}`,
|
||||
redirect_url: `${origin}/api/providers/github/setup?organizationId=${activeOrganization?.id ?? ""}&userId=${session?.user?.id ?? ""}`,
|
||||
name: `Dokploy-${format(new Date(), "yyyy-MM-dd")}-${randomString()}`,
|
||||
url: origin,
|
||||
hook_attributes: {
|
||||
@@ -52,7 +52,7 @@ export const AddGithubProvider = () => {
|
||||
);
|
||||
|
||||
setManifest(manifest);
|
||||
}, [data?.id]);
|
||||
}, [data?.id, activeOrganization?.id, session?.user?.id]);
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||
@@ -131,7 +131,11 @@ export const AddGithubProvider = () => {
|
||||
Unsure if you already have an app?
|
||||
</a>
|
||||
<Button
|
||||
disabled={isOrganization && organizationName.length < 1}
|
||||
disabled={
|
||||
(isOrganization && organizationName.length < 1) ||
|
||||
!activeOrganization?.id ||
|
||||
!session?.user?.id
|
||||
}
|
||||
type="submit"
|
||||
className="self-end"
|
||||
>
|
||||
|
||||
@@ -32,7 +32,6 @@ interface Props {
|
||||
}
|
||||
|
||||
export const BreadcrumbSidebar = ({ list }: Props) => {
|
||||
console.log(list);
|
||||
return (
|
||||
<header className="flex h-16 shrink-0 items-center gap-2 transition-[width,height] ease-linear group-has-[[data-collapsible=icon]]/sidebar-wrapper:h-12">
|
||||
<div className="flex items-center justify-between w-full">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dokploy",
|
||||
"version": "v0.28.1",
|
||||
"version": "v0.28.2",
|
||||
"private": true,
|
||||
"license": "Apache-2.0",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
# Proprietary Features
|
||||
|
||||
This directory contains all proprietary functionality of Dokploy.
|
||||
|
||||
## Purpose
|
||||
|
||||
This folder will house all **paid features** and premium functionality that are not part of the open source code.
|
||||
|
||||
## License
|
||||
|
||||
The code in this directory is under Dokploy's proprietary license. See [LICENSE_PROPRIETARY.md](../../../LICENSE_PROPRIETARY.md) for more details.
|
||||
|
||||
## Contact
|
||||
|
||||
If you want to learn more about our paid features or have any questions, please contact us at:
|
||||
|
||||
- Email: [sales@dokploy.com](mailto:sales@dokploy.com)
|
||||
- Contact Form: [https://dokploy.com/contact](https://dokploy.com/contact)
|
||||
@@ -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,138 @@ 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<number>`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,
|
||||
};
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -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,114 @@ 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<number>`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,
|
||||
};
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -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<number>`count(*)::int` })
|
||||
.from(environments)
|
||||
.innerJoin(projects, eq(environments.projectId, projects.projectId))
|
||||
.where(where),
|
||||
]);
|
||||
|
||||
return {
|
||||
items,
|
||||
total: countResult[0]?.count ?? 0,
|
||||
};
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -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,102 @@ 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<number>`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 };
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -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,97 @@ 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<number>`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 };
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -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,97 @@ 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<number>`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 };
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -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,104 @@ 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 };
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -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<number>`count(*)::int` })
|
||||
.from(projects)
|
||||
.where(where),
|
||||
]);
|
||||
|
||||
return {
|
||||
items,
|
||||
total: countResult[0]?.count ?? 0,
|
||||
};
|
||||
}),
|
||||
|
||||
remove: protectedProcedure
|
||||
.input(apiRemoveProject)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
|
||||
@@ -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,97 @@ 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<number>`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 };
|
||||
}),
|
||||
});
|
||||
|
||||
38
apps/dokploy/server/db/index.ts
Normal file
38
apps/dokploy/server/db/index.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { dbUrl } from "@dokploy/server/db/constants";
|
||||
import * as schema from "@dokploy/server/db/schema";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import { drizzle, type PostgresJsDatabase } from "drizzle-orm/postgres-js";
|
||||
import postgres from "postgres";
|
||||
|
||||
export { and, eq };
|
||||
|
||||
type Database = PostgresJsDatabase<typeof schema>;
|
||||
/**
|
||||
* Evita problemas de redeclaración global en monorepos.
|
||||
* No usamos `declare global`.
|
||||
*/
|
||||
const globalForDb = globalThis as unknown as {
|
||||
db?: Database;
|
||||
};
|
||||
|
||||
let dbConnection: Database;
|
||||
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
// En producción no usamos global cache
|
||||
dbConnection = drizzle(postgres(dbUrl), {
|
||||
schema,
|
||||
});
|
||||
} else {
|
||||
// En desarrollo reutilizamos conexión para evitar múltiples conexiones
|
||||
if (!globalForDb.db) {
|
||||
globalForDb.db = drizzle(postgres(dbUrl), {
|
||||
schema,
|
||||
});
|
||||
}
|
||||
|
||||
dbConnection = globalForDb.db;
|
||||
}
|
||||
|
||||
export const db: Database = dbConnection;
|
||||
|
||||
export { dbUrl };
|
||||
@@ -73,23 +73,25 @@ const { handler, api } = betterAuth({
|
||||
disabled: process.env.NODE_ENV === "production",
|
||||
},
|
||||
async trustedOrigins() {
|
||||
const trustedOrigins = await getTrustedOrigins();
|
||||
if (IS_CLOUD) {
|
||||
return trustedOrigins;
|
||||
return getTrustedOrigins();
|
||||
}
|
||||
const settings = await getWebServerSettings();
|
||||
if (!settings) {
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
...(settings?.serverIp ? [`http://${settings?.serverIp}:3000`] : []),
|
||||
...(settings?.host ? [`https://${settings?.host}`] : []),
|
||||
...(process.env.NODE_ENV === "development"
|
||||
const [trustedOrigins, settings] = await Promise.all([
|
||||
getTrustedOrigins(),
|
||||
getWebServerSettings(),
|
||||
]);
|
||||
if (!settings) return [];
|
||||
const devOrigins =
|
||||
process.env.NODE_ENV === "development"
|
||||
? [
|
||||
"http://localhost:3000",
|
||||
"https://absolutely-handy-falcon.ngrok-free.app",
|
||||
]
|
||||
: []),
|
||||
: [];
|
||||
return [
|
||||
...(settings?.serverIp ? [`http://${settings?.serverIp}:3000`] : []),
|
||||
...(settings?.host ? [`https://${settings?.host}`] : []),
|
||||
...devOrigins,
|
||||
...trustedOrigins,
|
||||
];
|
||||
},
|
||||
|
||||
@@ -117,23 +117,33 @@ export const getDokployUrl = async () => {
|
||||
return `http://${settings?.serverIp}:${process.env.PORT}`;
|
||||
};
|
||||
|
||||
export const getTrustedOrigins = async () => {
|
||||
const members = await db.query.member.findMany({
|
||||
where: eq(member.role, "owner"),
|
||||
with: {
|
||||
user: true,
|
||||
},
|
||||
});
|
||||
const TRUSTED_ORIGINS_CACHE_TTL_MS = 30 * 60_000;
|
||||
let trustedOriginsCache: { data: string[]; expiresAt: number } | null = null;
|
||||
|
||||
if (members.length === 0) {
|
||||
return [];
|
||||
export const getTrustedOrigins = async () => {
|
||||
const runQuery = async () => {
|
||||
const rows = await db
|
||||
.select({ trustedOrigins: user.trustedOrigins })
|
||||
.from(member)
|
||||
.innerJoin(user, eq(member.userId, user.id))
|
||||
.where(eq(member.role, "owner"));
|
||||
return Array.from(new Set(rows.flatMap((r) => r.trustedOrigins ?? [])));
|
||||
};
|
||||
|
||||
if (IS_CLOUD) {
|
||||
const now = Date.now();
|
||||
if (trustedOriginsCache && now < trustedOriginsCache.expiresAt) {
|
||||
return trustedOriginsCache.data;
|
||||
}
|
||||
const trustedOrigins = await runQuery();
|
||||
trustedOriginsCache = {
|
||||
data: trustedOrigins,
|
||||
expiresAt: now + TRUSTED_ORIGINS_CACHE_TTL_MS,
|
||||
};
|
||||
return trustedOrigins;
|
||||
}
|
||||
|
||||
const trustedOrigins = members.flatMap(
|
||||
(member) => member.user.trustedOrigins || [],
|
||||
);
|
||||
|
||||
return Array.from(new Set(trustedOrigins));
|
||||
return runQuery();
|
||||
};
|
||||
|
||||
export const getTrustedProviders = async () => {
|
||||
|
||||
@@ -106,10 +106,6 @@ export const writeDomainsToCompose = async (
|
||||
compose: Compose,
|
||||
domains: Domain[],
|
||||
) => {
|
||||
if (!domains.length) {
|
||||
return "";
|
||||
}
|
||||
|
||||
try {
|
||||
const composeConverted = await addDomainToCompose(compose, domains);
|
||||
const path = getComposePath(compose);
|
||||
@@ -145,7 +141,7 @@ export const addDomainToCompose = async (
|
||||
result = await loadDockerCompose(compose);
|
||||
}
|
||||
|
||||
if (!result || domains.length === 0) {
|
||||
if (!result) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user