refactor: adjust queries

This commit is contained in:
Mauricio Siu
2025-02-15 23:01:36 -06:00
parent 78c72b6337
commit 515d65d993
23 changed files with 538 additions and 138 deletions

View File

@@ -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>
);
}

View File

@@ -86,6 +86,7 @@ export const AddCertificate = () => {
privateKey: data.privateKey,
autoRenew: data.autoRenew,
serverId: data.serverId,
organizationId: "",
})
.then(async () => {
toast.success("Certificate Created");

View File

@@ -78,6 +78,7 @@ export const HandleSSHKeys = ({ sshKeyId }: Props) => {
const onSubmit = async (data: SSHKey) => {
await mutateAsync({
...data,
organizationId: "",
sshKeyId: sshKeyId || "",
})
.then(async () => {

View File

@@ -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>

View File

@@ -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({

View File

@@ -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>

View File

@@ -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

View File

@@ -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),
});
}),
});

View 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;
}),
});

View File

@@ -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

View File

@@ -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,

View File

@@ -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)

View File

@@ -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")

View File

@@ -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(),
});

View File

@@ -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({

View File

@@ -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, {

View File

@@ -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();

View File

@@ -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();

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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]);

View File

@@ -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;
};