mirror of
https://github.com/LukeHagar/dokploy.git
synced 2025-12-06 04:19:37 +00:00
refactor: adjust queries
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { PenBoxIcon, Plus, SquarePen } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { api } from "@/utils/api";
|
||||
import { toast } from "sonner";
|
||||
|
||||
interface Props {
|
||||
organizationId?: string;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
export function AddOrganization({ organizationId, children }: Props) {
|
||||
const utils = api.useUtils();
|
||||
const { data: organization } = api.organization.one.useQuery(
|
||||
{
|
||||
organizationId: organizationId ?? "",
|
||||
},
|
||||
{
|
||||
enabled: !!organizationId,
|
||||
},
|
||||
);
|
||||
const { mutateAsync, isLoading } = organizationId
|
||||
? api.organization.update.useMutation()
|
||||
: api.organization.create.useMutation();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [name, setName] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
if (organization) {
|
||||
setName(organization.name);
|
||||
}
|
||||
}, [organization]);
|
||||
const handleSubmit = async () => {
|
||||
await mutateAsync({ name, organizationId: organizationId ?? "" })
|
||||
.then(() => {
|
||||
setOpen(false);
|
||||
toast.success(
|
||||
`Organization ${organizationId ? "updated" : "created"} successfully`,
|
||||
);
|
||||
utils.organization.all.invalidate();
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
toast.error(
|
||||
`Failed to ${organizationId ? "update" : "create"} organization`,
|
||||
);
|
||||
});
|
||||
};
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
{organizationId ? (
|
||||
<DropdownMenuItem
|
||||
className="group cursor-pointer hover:bg-blue-500/10"
|
||||
onSelect={(e) => e.preventDefault()}
|
||||
>
|
||||
<PenBoxIcon className="size-3.5 text-primary group-hover:text-blue-500" />
|
||||
</DropdownMenuItem>
|
||||
) : (
|
||||
<DropdownMenuItem
|
||||
className="gap-2 p-2"
|
||||
onClick={() => {
|
||||
setOpen(true);
|
||||
}}
|
||||
onSelect={(e) => e.preventDefault()}
|
||||
>
|
||||
<div className="flex size-6 items-center justify-center rounded-md border bg-background">
|
||||
<Plus className="size-4" />
|
||||
</div>
|
||||
<div className="font-medium text-muted-foreground">
|
||||
Add organization
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-[425px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
{organizationId ? "Update organization" : "Add organization"}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
{organizationId
|
||||
? "Update the organization name"
|
||||
: "Create a new organization to manage your projects."}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="grid gap-4 py-4">
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="name" className="text-right">
|
||||
Name
|
||||
</Label>
|
||||
<Input
|
||||
id="name"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
className="col-span-3"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button type="submit" onClick={handleSubmit} isLoading={isLoading}>
|
||||
{organizationId ? "Update organization" : "Create organization"}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -86,6 +86,7 @@ export const AddCertificate = () => {
|
||||
privateKey: data.privateKey,
|
||||
autoRenew: data.autoRenew,
|
||||
serverId: data.serverId,
|
||||
organizationId: "",
|
||||
})
|
||||
.then(async () => {
|
||||
toast.success("Certificate Created");
|
||||
|
||||
@@ -78,6 +78,7 @@ export const HandleSSHKeys = ({ sshKeyId }: Props) => {
|
||||
const onSubmit = async (data: SSHKey) => {
|
||||
await mutateAsync({
|
||||
...data,
|
||||
organizationId: "",
|
||||
sshKeyId: sshKeyId || "",
|
||||
})
|
||||
.then(async () => {
|
||||
|
||||
@@ -76,10 +76,11 @@ export const ShowUsers = () => {
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[100px]">Email</TableHead>
|
||||
<TableHead className="text-center">Status</TableHead>
|
||||
<TableHead className="text-center">Role</TableHead>
|
||||
<TableHead className="text-center">2FA</TableHead>
|
||||
{/* <TableHead className="text-center">Status</TableHead> */}
|
||||
<TableHead className="text-center">
|
||||
Expiration
|
||||
Created At
|
||||
</TableHead>
|
||||
<TableHead className="text-right">Actions</TableHead>
|
||||
</TableRow>
|
||||
@@ -89,30 +90,32 @@ export const ShowUsers = () => {
|
||||
return (
|
||||
<TableRow key={user.userId}>
|
||||
<TableCell className="w-[100px]">
|
||||
{user.auth.email}
|
||||
{user.user.email}
|
||||
</TableCell>
|
||||
<TableCell className="text-center">
|
||||
<Badge
|
||||
variant={
|
||||
user.isRegistered ? "default" : "secondary"
|
||||
user.role === "owner"
|
||||
? "default"
|
||||
: "secondary"
|
||||
}
|
||||
>
|
||||
{user.isRegistered
|
||||
? "Registered"
|
||||
: "Not Registered"}
|
||||
{user.role}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="text-center">
|
||||
{user.auth.is2FAEnabled
|
||||
{user.user.is2FAEnabled
|
||||
? "2FA Enabled"
|
||||
: "2FA Not Enabled"}
|
||||
</TableCell>
|
||||
{/* <TableCell className="text-right">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{format(new Date(user.createdAt), "PPpp")}
|
||||
</span>
|
||||
</TableCell> */}
|
||||
<TableCell className="text-right">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{format(
|
||||
new Date(user.expirationDate),
|
||||
"PPpp",
|
||||
)}
|
||||
{format(new Date(user.createdAt), "PPpp")}
|
||||
</span>
|
||||
</TableCell>
|
||||
|
||||
@@ -131,7 +134,7 @@ export const ShowUsers = () => {
|
||||
<DropdownMenuLabel>
|
||||
Actions
|
||||
</DropdownMenuLabel>
|
||||
{!user.isRegistered && (
|
||||
{/* {!user.isRegistered && (
|
||||
<DropdownMenuItem
|
||||
className="w-full cursor-pointer"
|
||||
onSelect={(e) => {
|
||||
@@ -145,42 +148,44 @@ export const ShowUsers = () => {
|
||||
>
|
||||
Copy Invitation
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
)} */}
|
||||
|
||||
{user.isRegistered && (
|
||||
{/* {user.isRegistered && (
|
||||
<AddUserPermissions
|
||||
userId={user.userId}
|
||||
/>
|
||||
)}
|
||||
)} */}
|
||||
|
||||
<DialogAction
|
||||
title="Delete User"
|
||||
description="Are you sure you want to delete this user?"
|
||||
type="destructive"
|
||||
onClick={async () => {
|
||||
await mutateAsync({
|
||||
authId: user.authId,
|
||||
})
|
||||
.then(() => {
|
||||
toast.success(
|
||||
"User deleted successfully",
|
||||
);
|
||||
refetch();
|
||||
{user.role !== "owner" && (
|
||||
<DialogAction
|
||||
title="Delete User"
|
||||
description="Are you sure you want to delete this user?"
|
||||
type="destructive"
|
||||
onClick={async () => {
|
||||
await mutateAsync({
|
||||
userId: user.userId,
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error(
|
||||
"Error deleting destination",
|
||||
);
|
||||
});
|
||||
}}
|
||||
>
|
||||
<DropdownMenuItem
|
||||
className="w-full cursor-pointer text-red-500 hover:!text-red-600"
|
||||
onSelect={(e) => e.preventDefault()}
|
||||
.then(() => {
|
||||
toast.success(
|
||||
"User deleted successfully",
|
||||
);
|
||||
refetch();
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error(
|
||||
"Error deleting destination",
|
||||
);
|
||||
});
|
||||
}}
|
||||
>
|
||||
Delete User
|
||||
</DropdownMenuItem>
|
||||
</DialogAction>
|
||||
<DropdownMenuItem
|
||||
className="w-full cursor-pointer text-red-500 hover:!text-red-600"
|
||||
onSelect={(e) => e.preventDefault()}
|
||||
>
|
||||
Delete User
|
||||
</DropdownMenuItem>
|
||||
</DialogAction>
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</TableCell>
|
||||
|
||||
@@ -52,7 +52,7 @@ type AddServerDomain = z.infer<typeof addServerDomain>;
|
||||
|
||||
export const WebDomain = () => {
|
||||
const { t } = useTranslation("settings");
|
||||
const { data: user, refetch } = api.admin.one.useQuery();
|
||||
const { data, refetch } = api.auth.get.useQuery();
|
||||
const { mutateAsync, isLoading } =
|
||||
api.settings.assignDomainServer.useMutation();
|
||||
|
||||
@@ -65,14 +65,14 @@ export const WebDomain = () => {
|
||||
resolver: zodResolver(addServerDomain),
|
||||
});
|
||||
useEffect(() => {
|
||||
if (user) {
|
||||
if (data) {
|
||||
form.reset({
|
||||
domain: user?.host || "",
|
||||
certificateType: user?.certificateType,
|
||||
letsEncryptEmail: user?.letsEncryptEmail || "",
|
||||
domain: data?.user?.host || "",
|
||||
certificateType: data?.user?.certificateType,
|
||||
letsEncryptEmail: data?.user?.letsEncryptEmail || "",
|
||||
});
|
||||
}
|
||||
}, [form, form.reset, user]);
|
||||
}, [form, form.reset, data]);
|
||||
|
||||
const onSubmit = async (data: AddServerDomain) => {
|
||||
await mutateAsync({
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
import {
|
||||
Activity,
|
||||
AudioWaveform,
|
||||
BarChartHorizontalBigIcon,
|
||||
Bell,
|
||||
BlocksIcon,
|
||||
@@ -8,6 +9,7 @@ import {
|
||||
Boxes,
|
||||
ChevronRight,
|
||||
CircleHelp,
|
||||
Command,
|
||||
CreditCard,
|
||||
Database,
|
||||
Folder,
|
||||
@@ -16,11 +18,13 @@ import {
|
||||
GitBranch,
|
||||
HeartIcon,
|
||||
KeyRound,
|
||||
Loader2,
|
||||
type LucideIcon,
|
||||
Package,
|
||||
PieChart,
|
||||
Server,
|
||||
ShieldCheck,
|
||||
Trash2,
|
||||
User,
|
||||
Users,
|
||||
} from "lucide-react";
|
||||
@@ -480,37 +484,207 @@ interface Props {
|
||||
function LogoWrapper() {
|
||||
return <SidebarLogo />;
|
||||
}
|
||||
import { ChevronsUpDown, Plus } from "lucide-react";
|
||||
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuShortcut,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { AddOrganization } from "../dashboard/organization/handle-organization";
|
||||
import { authClient } from "@/lib/auth";
|
||||
import { DialogAction } from "../shared/dialog-action";
|
||||
import { Button } from "../ui/button";
|
||||
import { toast } from "sonner";
|
||||
const data = {
|
||||
user: {
|
||||
name: "shadcn",
|
||||
email: "m@example.com",
|
||||
avatar: "/avatars/shadcn.jpg",
|
||||
},
|
||||
teams: [
|
||||
{
|
||||
name: "Acme Inc",
|
||||
logo: GalleryVerticalEnd,
|
||||
plan: "Enterprise",
|
||||
},
|
||||
{
|
||||
name: "Acme Corp.",
|
||||
logo: AudioWaveform,
|
||||
plan: "Startup",
|
||||
},
|
||||
{
|
||||
name: "Evil Corp.",
|
||||
logo: Command,
|
||||
plan: "Free",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const teams = data.teams;
|
||||
function SidebarLogo() {
|
||||
const { state } = useSidebar();
|
||||
const { data: dokployVersion } = api.settings.getDokployVersion.useQuery();
|
||||
const {
|
||||
data: organizations,
|
||||
refetch,
|
||||
isLoading,
|
||||
} = api.organization.all.useQuery();
|
||||
const { mutateAsync: deleteOrganization, isLoading: isRemoving } =
|
||||
api.organization.delete.useMutation();
|
||||
const { isMobile } = useSidebar();
|
||||
const { data: activeOrganization } = authClient.useActiveOrganization();
|
||||
|
||||
const [activeTeam, setActiveTeam] = useState<
|
||||
typeof activeOrganization | null
|
||||
>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeOrganization) {
|
||||
setActiveTeam(activeOrganization);
|
||||
}
|
||||
}, [activeOrganization]);
|
||||
|
||||
return (
|
||||
<Link
|
||||
href="/dashboard/projects"
|
||||
className="flex items-center gap-2 p-1 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground group-data-[collapsible=icon]/35 rounded-lg "
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"flex aspect-square items-center justify-center rounded-lg transition-all",
|
||||
state === "collapsed" ? "size-6" : "size-10",
|
||||
)}
|
||||
<>
|
||||
{isLoading ? (
|
||||
<div className="flex flex-row gap-2 items-center justify-center text-sm text-muted-foreground min-h-[5vh] pt-4">
|
||||
<span>Loading...</span>
|
||||
<Loader2 className="animate-spin size-4" />
|
||||
</div>
|
||||
) : (
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<SidebarMenuButton
|
||||
size="lg"
|
||||
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
||||
>
|
||||
{/* <div className="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground"> */}
|
||||
<div
|
||||
className={cn(
|
||||
"flex aspect-square items-center justify-center rounded-lg transition-all",
|
||||
state === "collapsed" ? "size-6" : "size-10",
|
||||
)}
|
||||
>
|
||||
<Logo
|
||||
className={cn(
|
||||
"transition-all",
|
||||
state === "collapsed" ? "size-6" : "size-10",
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||
<span className="truncate font-semibold">
|
||||
{activeTeam?.name}
|
||||
</span>
|
||||
</div>
|
||||
<ChevronsUpDown className="ml-auto" />
|
||||
</SidebarMenuButton>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
className="w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg"
|
||||
align="start"
|
||||
side={isMobile ? "bottom" : "right"}
|
||||
sideOffset={4}
|
||||
>
|
||||
<DropdownMenuLabel className="text-xs text-muted-foreground">
|
||||
Organizations
|
||||
</DropdownMenuLabel>
|
||||
{organizations?.map((org, index) => (
|
||||
<div className="flex flex-row justify-between" key={org.name}>
|
||||
<DropdownMenuItem
|
||||
onClick={async () => {
|
||||
await authClient.organization.setActive({
|
||||
organizationId: org.id,
|
||||
});
|
||||
|
||||
window.location.reload();
|
||||
}}
|
||||
className="w-full gap-2 p-2"
|
||||
>
|
||||
<div className="flex size-6 items-center justify-center rounded-sm border">
|
||||
<Logo
|
||||
className={cn(
|
||||
"transition-all",
|
||||
state === "collapsed" ? "size-6" : "size-10",
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
{org.name}
|
||||
{/* <DropdownMenuShortcut>⌘{index + 1}</DropdownMenuShortcut> */}
|
||||
</DropdownMenuItem>
|
||||
{/* <DropdownMenuSeparator /> */}
|
||||
<div className="flex flex-row gap-2">
|
||||
<AddOrganization organizationId={org.id} />
|
||||
<DialogAction
|
||||
title="Delete Organization"
|
||||
description="Are you sure you want to delete this organization?"
|
||||
type="destructive"
|
||||
onClick={async () => {
|
||||
await deleteOrganization({
|
||||
organizationId: org.id,
|
||||
})
|
||||
.then(() => {
|
||||
refetch();
|
||||
toast.success("Port deleted successfully");
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error deleting port");
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="group hover:bg-red-500/10 "
|
||||
isLoading={isRemoving}
|
||||
>
|
||||
<Trash2 className="size-4 text-primary group-hover:text-red-500" />
|
||||
</Button>
|
||||
</DialogAction>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<DropdownMenuSeparator />
|
||||
<AddOrganization />
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
)}
|
||||
|
||||
{/* <Link
|
||||
href="/dashboard/projects"
|
||||
className="flex items-center gap-2 p-1 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground group-data-[collapsible=icon]/35 rounded-lg "
|
||||
>
|
||||
<Logo
|
||||
<div
|
||||
className={cn(
|
||||
"transition-all",
|
||||
"flex aspect-square items-center justify-center rounded-lg transition-all",
|
||||
state === "collapsed" ? "size-6" : "size-10",
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
>
|
||||
<Logo
|
||||
className={cn(
|
||||
"transition-all",
|
||||
state === "collapsed" ? "size-6" : "size-10",
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="text-left text-sm leading-tight group-data-[state=open]/collapsible:rotate-90">
|
||||
<p className="truncate font-semibold">Dokploy</p>
|
||||
<p className="truncate text-xs text-muted-foreground">
|
||||
{dokployVersion}
|
||||
</p>
|
||||
</div>
|
||||
</Link>
|
||||
<div className="text-left text-sm leading-tight group-data-[state=open]/collapsible:rotate-90">
|
||||
<p className="truncate font-semibold">Dokploy</p>
|
||||
<p className="truncate text-xs text-muted-foreground">
|
||||
{dokployVersion}
|
||||
</p>
|
||||
</div>
|
||||
</Link> */}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -577,12 +751,12 @@ export default function Page({ children }: Props) {
|
||||
>
|
||||
<Sidebar collapsible="icon" variant="floating">
|
||||
<SidebarHeader>
|
||||
<SidebarMenuButton
|
||||
{/* <SidebarMenuButton
|
||||
className="group-data-[collapsible=icon]:!p-0"
|
||||
size="lg"
|
||||
>
|
||||
<LogoWrapper />
|
||||
</SidebarMenuButton>
|
||||
> */}
|
||||
<LogoWrapper />
|
||||
{/* </SidebarMenuButton> */}
|
||||
</SidebarHeader>
|
||||
<SidebarContent>
|
||||
<SidebarGroup>
|
||||
|
||||
@@ -33,7 +33,7 @@ import { sshRouter } from "./routers/ssh-key";
|
||||
import { stripeRouter } from "./routers/stripe";
|
||||
import { swarmRouter } from "./routers/swarm";
|
||||
import { userRouter } from "./routers/user";
|
||||
|
||||
import { organizationRouter } from "./routers/organization";
|
||||
/**
|
||||
* This is the primary router for your server.
|
||||
*
|
||||
@@ -75,6 +75,7 @@ export const appRouter = createTRPCRouter({
|
||||
server: serverRouter,
|
||||
stripe: stripeRouter,
|
||||
swarm: swarmRouter,
|
||||
organization: organizationRouter,
|
||||
});
|
||||
|
||||
// export type definition of API
|
||||
|
||||
@@ -32,10 +32,7 @@ export const certificateRouter = createTRPCRouter({
|
||||
.input(apiFindCertificate)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const certificates = await findCertificateById(input.certificateId);
|
||||
if (
|
||||
IS_CLOUD &&
|
||||
certificates.organizationId !== ctx.session.activeOrganizationId
|
||||
) {
|
||||
if (certificates.organizationId !== ctx.session.activeOrganizationId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not allowed to access this certificate",
|
||||
@@ -47,10 +44,7 @@ export const certificateRouter = createTRPCRouter({
|
||||
.input(apiFindCertificate)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const certificates = await findCertificateById(input.certificateId);
|
||||
if (
|
||||
IS_CLOUD &&
|
||||
certificates.organizationId !== ctx.session.activeOrganizationId
|
||||
) {
|
||||
if (certificates.organizationId !== ctx.session.activeOrganizationId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not allowed to delete this certificate",
|
||||
@@ -61,13 +55,7 @@ export const certificateRouter = createTRPCRouter({
|
||||
}),
|
||||
all: adminProcedure.query(async ({ ctx }) => {
|
||||
return await db.query.certificates.findMany({
|
||||
// TODO: Remove this line when the cloud version is ready
|
||||
...(IS_CLOUD && {
|
||||
where: eq(
|
||||
certificates.organizationId,
|
||||
ctx.session.activeOrganizationId,
|
||||
),
|
||||
}),
|
||||
where: eq(certificates.organizationId, ctx.session.activeOrganizationId),
|
||||
});
|
||||
}),
|
||||
});
|
||||
|
||||
86
apps/dokploy/server/api/routers/organization.ts
Normal file
86
apps/dokploy/server/api/routers/organization.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { adminProcedure, createTRPCRouter } from "../trpc";
|
||||
import { z } from "zod";
|
||||
import { db } from "@/server/db";
|
||||
import { member, organization } from "@/server/db/schema";
|
||||
import { nanoid } from "nanoid";
|
||||
import { desc, eq } from "drizzle-orm";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
export const organizationRouter = createTRPCRouter({
|
||||
create: adminProcedure
|
||||
.input(
|
||||
z.object({
|
||||
name: z.string(),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const result = await db
|
||||
.insert(organization)
|
||||
.values({
|
||||
...input,
|
||||
slug: nanoid(),
|
||||
createdAt: new Date(),
|
||||
ownerId: ctx.user.ownerId,
|
||||
})
|
||||
.returning()
|
||||
.then((res) => res[0]);
|
||||
|
||||
if (!result) {
|
||||
throw new TRPCError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
message: "Failed to create organization",
|
||||
});
|
||||
}
|
||||
|
||||
const memberResult = await db.insert(member).values({
|
||||
organizationId: result.id,
|
||||
role: "owner",
|
||||
createdAt: new Date(),
|
||||
userId: ctx.user.id,
|
||||
});
|
||||
return result;
|
||||
}),
|
||||
all: adminProcedure.query(async ({ ctx }) => {
|
||||
return await db.query.organization.findMany({
|
||||
where: eq(organization.ownerId, ctx.user.ownerId),
|
||||
orderBy: [desc(organization.createdAt)],
|
||||
});
|
||||
}),
|
||||
one: adminProcedure
|
||||
.input(
|
||||
z.object({
|
||||
organizationId: z.string(),
|
||||
}),
|
||||
)
|
||||
.query(async ({ ctx, input }) => {
|
||||
return await db.query.organization.findFirst({
|
||||
where: eq(organization.id, input.organizationId),
|
||||
});
|
||||
}),
|
||||
update: adminProcedure
|
||||
.input(
|
||||
z.object({
|
||||
organizationId: z.string(),
|
||||
name: z.string(),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const result = await db
|
||||
.update(organization)
|
||||
.set({ name: input.name })
|
||||
.where(eq(organization.id, input.organizationId))
|
||||
.returning();
|
||||
return result[0];
|
||||
}),
|
||||
delete: adminProcedure
|
||||
.input(
|
||||
z.object({
|
||||
organizationId: z.string(),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const result = await db
|
||||
.delete(organization)
|
||||
.where(eq(organization.id, input.organizationId));
|
||||
return result;
|
||||
}),
|
||||
});
|
||||
@@ -16,8 +16,8 @@ import {
|
||||
updateRegistry,
|
||||
} from "@dokploy/server";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { adminProcedure, createTRPCRouter, protectedProcedure } from "../trpc";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { adminProcedure, createTRPCRouter, protectedProcedure } from "../trpc";
|
||||
|
||||
export const registryRouter = createTRPCRouter({
|
||||
create: adminProcedure
|
||||
|
||||
@@ -24,7 +24,6 @@ export const sshRouter = createTRPCRouter({
|
||||
.input(apiCreateSshKey)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
try {
|
||||
console.log(ctx.user.ownerId);
|
||||
await createSshKey({
|
||||
...input,
|
||||
organizationId: ctx.session.activeOrganizationId,
|
||||
|
||||
@@ -2,10 +2,17 @@ import { apiFindOneUser, apiFindOneUserByAuth } from "@/server/db/schema";
|
||||
import { findUserByAuthId, findUserById, findUsers } from "@dokploy/server";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { adminProcedure, createTRPCRouter, protectedProcedure } from "../trpc";
|
||||
|
||||
import { eq } from "drizzle-orm";
|
||||
import { member } from "@dokploy/server/db/schema";
|
||||
import { db } from "@dokploy/server/db";
|
||||
export const userRouter = createTRPCRouter({
|
||||
all: adminProcedure.query(async ({ ctx }) => {
|
||||
return await findUsers(ctx.user.adminId);
|
||||
return await db.query.member.findMany({
|
||||
where: eq(member.organizationId, ctx.session.activeOrganizationId),
|
||||
with: {
|
||||
user: true,
|
||||
},
|
||||
});
|
||||
}),
|
||||
byAuthId: protectedProcedure
|
||||
.input(apiFindOneUserByAuth)
|
||||
|
||||
@@ -2,6 +2,8 @@ import { relations } from "drizzle-orm";
|
||||
import { boolean, pgTable, text, timestamp } from "drizzle-orm/pg-core";
|
||||
import { nanoid } from "nanoid";
|
||||
import { users_temp } from "./user";
|
||||
import { server } from "./server";
|
||||
import { projects } from "./project";
|
||||
|
||||
export const account = pgTable("account", {
|
||||
id: text("id")
|
||||
@@ -60,12 +62,17 @@ export const organization = pgTable("organization", {
|
||||
.references(() => users_temp.id),
|
||||
});
|
||||
|
||||
export const organizationRelations = relations(organization, ({ one }) => ({
|
||||
owner: one(users_temp, {
|
||||
fields: [organization.ownerId],
|
||||
references: [users_temp.id],
|
||||
export const organizationRelations = relations(
|
||||
organization,
|
||||
({ one, many }) => ({
|
||||
owner: one(users_temp, {
|
||||
fields: [organization.ownerId],
|
||||
references: [users_temp.id],
|
||||
}),
|
||||
servers: many(server),
|
||||
projects: many(projects),
|
||||
}),
|
||||
}));
|
||||
);
|
||||
|
||||
export const member = pgTable("member", {
|
||||
id: text("id")
|
||||
|
||||
@@ -61,5 +61,5 @@ export const apiUpdateBitbucket = createSchema.extend({
|
||||
name: z.string().min(1),
|
||||
bitbucketUsername: z.string().optional(),
|
||||
bitbucketWorkspaceName: z.string().optional(),
|
||||
userId: z.string().optional(),
|
||||
organizationId: z.string().optional(),
|
||||
});
|
||||
|
||||
@@ -150,7 +150,7 @@ export const apiCreateSlack = notificationsSchema
|
||||
export const apiUpdateSlack = apiCreateSlack.partial().extend({
|
||||
notificationId: z.string().min(1),
|
||||
slackId: z.string(),
|
||||
userId: z.string().optional(),
|
||||
organizationId: z.string().optional(),
|
||||
});
|
||||
|
||||
export const apiTestSlackConnection = apiCreateSlack.pick({
|
||||
@@ -177,7 +177,7 @@ export const apiCreateTelegram = notificationsSchema
|
||||
export const apiUpdateTelegram = apiCreateTelegram.partial().extend({
|
||||
notificationId: z.string().min(1),
|
||||
telegramId: z.string().min(1),
|
||||
userId: z.string().optional(),
|
||||
organizationId: z.string().optional(),
|
||||
});
|
||||
|
||||
export const apiTestTelegramConnection = apiCreateTelegram.pick({
|
||||
@@ -204,7 +204,7 @@ export const apiCreateDiscord = notificationsSchema
|
||||
export const apiUpdateDiscord = apiCreateDiscord.partial().extend({
|
||||
notificationId: z.string().min(1),
|
||||
discordId: z.string().min(1),
|
||||
userId: z.string().optional(),
|
||||
organizationId: z.string().optional(),
|
||||
});
|
||||
|
||||
export const apiTestDiscordConnection = apiCreateDiscord
|
||||
@@ -238,7 +238,7 @@ export const apiCreateEmail = notificationsSchema
|
||||
export const apiUpdateEmail = apiCreateEmail.partial().extend({
|
||||
notificationId: z.string().min(1),
|
||||
emailId: z.string().min(1),
|
||||
userId: z.string().optional(),
|
||||
organizationId: z.string().optional(),
|
||||
});
|
||||
|
||||
export const apiTestEmailConnection = apiCreateEmail.pick({
|
||||
|
||||
@@ -118,6 +118,10 @@ export const serverRelations = relations(server, ({ one, many }) => ({
|
||||
mysql: many(mysql),
|
||||
postgres: many(postgres),
|
||||
certificates: many(certificates),
|
||||
organization: one(organization, {
|
||||
fields: [server.organizationId],
|
||||
references: [organization.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
const createSchema = createInsertSchema(server, {
|
||||
|
||||
@@ -12,14 +12,14 @@ export type Bitbucket = typeof bitbucket.$inferSelect;
|
||||
|
||||
export const createBitbucket = async (
|
||||
input: typeof apiCreateBitbucket._type,
|
||||
userId: string,
|
||||
organizationId: string,
|
||||
) => {
|
||||
return await db.transaction(async (tx) => {
|
||||
const newGitProvider = await tx
|
||||
.insert(gitProvider)
|
||||
.values({
|
||||
providerType: "bitbucket",
|
||||
userId: userId,
|
||||
organizationId: organizationId,
|
||||
name: input.name,
|
||||
})
|
||||
.returning()
|
||||
@@ -74,12 +74,12 @@ export const updateBitbucket = async (
|
||||
.where(eq(bitbucket.bitbucketId, bitbucketId))
|
||||
.returning();
|
||||
|
||||
if (input.name || input.userId) {
|
||||
if (input.name || input.organizationId) {
|
||||
await tx
|
||||
.update(gitProvider)
|
||||
.set({
|
||||
name: input.name,
|
||||
userId: input.userId,
|
||||
organizationId: input.organizationId,
|
||||
})
|
||||
.where(eq(gitProvider.gitProviderId, input.gitProviderId))
|
||||
.returning();
|
||||
|
||||
@@ -10,13 +10,13 @@ export type Destination = typeof destinations.$inferSelect;
|
||||
|
||||
export const createDestintation = async (
|
||||
input: typeof apiCreateDestination._type,
|
||||
userId: string,
|
||||
organizationId: string,
|
||||
) => {
|
||||
const newDestination = await db
|
||||
.insert(destinations)
|
||||
.values({
|
||||
...input,
|
||||
userId: userId,
|
||||
organizationId: organizationId,
|
||||
})
|
||||
.returning()
|
||||
.then((value) => value[0]);
|
||||
@@ -46,14 +46,14 @@ export const findDestinationById = async (destinationId: string) => {
|
||||
|
||||
export const removeDestinationById = async (
|
||||
destinationId: string,
|
||||
userId: string,
|
||||
organizationId: string,
|
||||
) => {
|
||||
const result = await db
|
||||
.delete(destinations)
|
||||
.where(
|
||||
and(
|
||||
eq(destinations.destinationId, destinationId),
|
||||
eq(destinations.userId, userId),
|
||||
eq(destinations.organizationId, organizationId),
|
||||
),
|
||||
)
|
||||
.returning();
|
||||
@@ -73,7 +73,7 @@ export const updateDestinationById = async (
|
||||
.where(
|
||||
and(
|
||||
eq(destinations.destinationId, destinationId),
|
||||
eq(destinations.userId, destinationData.userId || ""),
|
||||
eq(destinations.organizationId, destinationData.organizationId || ""),
|
||||
),
|
||||
)
|
||||
.returning();
|
||||
|
||||
@@ -12,14 +12,14 @@ import { updatePreviewDeployment } from "./preview-deployment";
|
||||
export type Github = typeof github.$inferSelect;
|
||||
export const createGithub = async (
|
||||
input: typeof apiCreateGithub._type,
|
||||
userId: string,
|
||||
organizationId: string,
|
||||
) => {
|
||||
return await db.transaction(async (tx) => {
|
||||
const newGitProvider = await tx
|
||||
.insert(gitProvider)
|
||||
.values({
|
||||
providerType: "github",
|
||||
userId: userId,
|
||||
organizationId: organizationId,
|
||||
name: input.name,
|
||||
})
|
||||
.returning()
|
||||
|
||||
@@ -13,14 +13,14 @@ export type Gitlab = typeof gitlab.$inferSelect;
|
||||
|
||||
export const createGitlab = async (
|
||||
input: typeof apiCreateGitlab._type,
|
||||
userId: string,
|
||||
organizationId: string,
|
||||
) => {
|
||||
return await db.transaction(async (tx) => {
|
||||
const newGitProvider = await tx
|
||||
.insert(gitProvider)
|
||||
.values({
|
||||
providerType: "gitlab",
|
||||
userId: userId,
|
||||
organizationId: organizationId,
|
||||
name: input.name,
|
||||
})
|
||||
.returning()
|
||||
|
||||
@@ -24,7 +24,7 @@ export type Notification = typeof notifications.$inferSelect;
|
||||
|
||||
export const createSlackNotification = async (
|
||||
input: typeof apiCreateSlack._type,
|
||||
userId: string,
|
||||
organizationId: string,
|
||||
) => {
|
||||
await db.transaction(async (tx) => {
|
||||
const newSlack = await tx
|
||||
@@ -54,7 +54,7 @@ export const createSlackNotification = async (
|
||||
dokployRestart: input.dokployRestart,
|
||||
dockerCleanup: input.dockerCleanup,
|
||||
notificationType: "slack",
|
||||
userId: userId,
|
||||
organizationId: organizationId,
|
||||
serverThreshold: input.serverThreshold,
|
||||
})
|
||||
.returning()
|
||||
@@ -84,7 +84,7 @@ export const updateSlackNotification = async (
|
||||
databaseBackup: input.databaseBackup,
|
||||
dokployRestart: input.dokployRestart,
|
||||
dockerCleanup: input.dockerCleanup,
|
||||
userId: input.userId,
|
||||
organizationId: input.organizationId,
|
||||
serverThreshold: input.serverThreshold,
|
||||
})
|
||||
.where(eq(notifications.notificationId, input.notificationId))
|
||||
@@ -114,7 +114,7 @@ export const updateSlackNotification = async (
|
||||
|
||||
export const createTelegramNotification = async (
|
||||
input: typeof apiCreateTelegram._type,
|
||||
userId: string,
|
||||
organizationId: string,
|
||||
) => {
|
||||
await db.transaction(async (tx) => {
|
||||
const newTelegram = await tx
|
||||
@@ -144,7 +144,7 @@ export const createTelegramNotification = async (
|
||||
dokployRestart: input.dokployRestart,
|
||||
dockerCleanup: input.dockerCleanup,
|
||||
notificationType: "telegram",
|
||||
userId: userId,
|
||||
organizationId: organizationId,
|
||||
serverThreshold: input.serverThreshold,
|
||||
})
|
||||
.returning()
|
||||
@@ -174,7 +174,7 @@ export const updateTelegramNotification = async (
|
||||
databaseBackup: input.databaseBackup,
|
||||
dokployRestart: input.dokployRestart,
|
||||
dockerCleanup: input.dockerCleanup,
|
||||
userId: input.userId,
|
||||
organizationId: input.organizationId,
|
||||
serverThreshold: input.serverThreshold,
|
||||
})
|
||||
.where(eq(notifications.notificationId, input.notificationId))
|
||||
@@ -204,7 +204,7 @@ export const updateTelegramNotification = async (
|
||||
|
||||
export const createDiscordNotification = async (
|
||||
input: typeof apiCreateDiscord._type,
|
||||
userId: string,
|
||||
organizationId: string,
|
||||
) => {
|
||||
await db.transaction(async (tx) => {
|
||||
const newDiscord = await tx
|
||||
@@ -234,7 +234,7 @@ export const createDiscordNotification = async (
|
||||
dokployRestart: input.dokployRestart,
|
||||
dockerCleanup: input.dockerCleanup,
|
||||
notificationType: "discord",
|
||||
userId: userId,
|
||||
organizationId: organizationId,
|
||||
serverThreshold: input.serverThreshold,
|
||||
})
|
||||
.returning()
|
||||
@@ -264,7 +264,7 @@ export const updateDiscordNotification = async (
|
||||
databaseBackup: input.databaseBackup,
|
||||
dokployRestart: input.dokployRestart,
|
||||
dockerCleanup: input.dockerCleanup,
|
||||
userId: input.userId,
|
||||
organizationId: input.organizationId,
|
||||
serverThreshold: input.serverThreshold,
|
||||
})
|
||||
.where(eq(notifications.notificationId, input.notificationId))
|
||||
@@ -294,7 +294,7 @@ export const updateDiscordNotification = async (
|
||||
|
||||
export const createEmailNotification = async (
|
||||
input: typeof apiCreateEmail._type,
|
||||
userId: string,
|
||||
organizationId: string,
|
||||
) => {
|
||||
await db.transaction(async (tx) => {
|
||||
const newEmail = await tx
|
||||
@@ -328,7 +328,7 @@ export const createEmailNotification = async (
|
||||
dokployRestart: input.dokployRestart,
|
||||
dockerCleanup: input.dockerCleanup,
|
||||
notificationType: "email",
|
||||
userId: userId,
|
||||
organizationId: organizationId,
|
||||
serverThreshold: input.serverThreshold,
|
||||
})
|
||||
.returning()
|
||||
@@ -358,7 +358,7 @@ export const updateEmailNotification = async (
|
||||
databaseBackup: input.databaseBackup,
|
||||
dokployRestart: input.dokployRestart,
|
||||
dockerCleanup: input.dockerCleanup,
|
||||
userId: input.userId,
|
||||
organizationId: input.organizationId,
|
||||
serverThreshold: input.serverThreshold,
|
||||
})
|
||||
.where(eq(notifications.notificationId, input.notificationId))
|
||||
@@ -392,7 +392,7 @@ export const updateEmailNotification = async (
|
||||
|
||||
export const createGotifyNotification = async (
|
||||
input: typeof apiCreateGotify._type,
|
||||
userId: string,
|
||||
organizationId: string,
|
||||
) => {
|
||||
await db.transaction(async (tx) => {
|
||||
const newGotify = await tx
|
||||
@@ -424,7 +424,7 @@ export const createGotifyNotification = async (
|
||||
dokployRestart: input.dokployRestart,
|
||||
dockerCleanup: input.dockerCleanup,
|
||||
notificationType: "gotify",
|
||||
userId: userId,
|
||||
organizationId: organizationId,
|
||||
})
|
||||
.returning()
|
||||
.then((value) => value[0]);
|
||||
@@ -453,7 +453,7 @@ export const updateGotifyNotification = async (
|
||||
databaseBackup: input.databaseBackup,
|
||||
dokployRestart: input.dokployRestart,
|
||||
dockerCleanup: input.dockerCleanup,
|
||||
userId: input.userId,
|
||||
organizationId: input.organizationId,
|
||||
})
|
||||
.where(eq(notifications.notificationId, input.notificationId))
|
||||
.returning()
|
||||
|
||||
@@ -16,13 +16,13 @@ export type Project = typeof projects.$inferSelect;
|
||||
|
||||
export const createProject = async (
|
||||
input: typeof apiCreateProject._type,
|
||||
userId: string,
|
||||
organizationId: string,
|
||||
) => {
|
||||
const newProject = await db
|
||||
.insert(projects)
|
||||
.values({
|
||||
...input,
|
||||
userId: userId,
|
||||
organizationId: organizationId,
|
||||
})
|
||||
.returning()
|
||||
.then((value) => value[0]);
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { db } from "@dokploy/server/db";
|
||||
import { type apiCreateServer, server } from "@dokploy/server/db/schema";
|
||||
import {
|
||||
type apiCreateServer,
|
||||
organization,
|
||||
server,
|
||||
} from "@dokploy/server/db/schema";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { desc, eq } from "drizzle-orm";
|
||||
|
||||
@@ -7,13 +11,13 @@ export type Server = typeof server.$inferSelect;
|
||||
|
||||
export const createServer = async (
|
||||
input: typeof apiCreateServer._type,
|
||||
userId: string,
|
||||
organizationId: string,
|
||||
) => {
|
||||
const newServer = await db
|
||||
.insert(server)
|
||||
.values({
|
||||
...input,
|
||||
userId: userId,
|
||||
organizationId: organizationId,
|
||||
createdAt: new Date().toISOString(),
|
||||
})
|
||||
.returning()
|
||||
@@ -47,11 +51,15 @@ export const findServerById = async (serverId: string) => {
|
||||
};
|
||||
|
||||
export const findServersByUserId = async (userId: string) => {
|
||||
const servers = await db.query.server.findMany({
|
||||
where: eq(server.userId, userId),
|
||||
orderBy: desc(server.createdAt),
|
||||
const orgs = await db.query.organization.findMany({
|
||||
where: eq(organization.ownerId, userId),
|
||||
with: {
|
||||
servers: true,
|
||||
},
|
||||
});
|
||||
|
||||
const servers = orgs.flatMap((org) => org.servers);
|
||||
|
||||
return servers;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user