mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +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)
|
||||
|
||||
Reference in New Issue
Block a user