refactor: add organizations system

This commit is contained in:
Mauricio Siu
2025-02-09 20:53:06 -06:00
parent fafc238e70
commit 8bd72a8a34
47 changed files with 359 additions and 171 deletions

View File

@@ -21,6 +21,7 @@ import {
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { authClient } from "@/lib/auth";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { PlusIcon, SquarePen } from "lucide-react";
@@ -97,6 +98,18 @@ export const HandleProject = ({ projectId }: Props) => {
);
});
};
// useEffect(() => {
// const getUsers = async () => {
// const users = await authClient.admin.listUsers({
// query: {
// limit: 100,
// },
// });
// console.log(users);
// };
// getUsers();
// });
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
@@ -110,10 +123,26 @@ export const HandleProject = ({ projectId }: Props) => {
<span>Update</span>
</DropdownMenuItem>
) : (
<>
<Button>
<PlusIcon className="h-4 w-4" />
Create Project
</Button>
<Button
onClick={async () => {
const newUser = await authClient.admin.createUser({
name: "Test User",
email: "test4@example.com",
password: "password123",
role: "user",
});
console.log(newUser);
}}
>
Create user
</Button>
</>
)}
</DialogTrigger>
<DialogContent className="sm:m:max-w-lg ">

View File

@@ -57,7 +57,7 @@ export const ShowProjects = () => {
authId: auth?.id || "",
},
{
enabled: !!auth?.id && auth?.rol === "user",
enabled: !!auth?.id && auth?.role === "user",
},
);
const { mutateAsync } = api.project.remove.useMutation();
@@ -91,7 +91,7 @@ export const ShowProjects = () => {
</CardDescription>
</CardHeader>
{(auth?.rol === "admin" || user?.canCreateProjects) && (
{(auth?.role === "admin" || user?.canCreateProjects) && (
<div className="">
<HandleProject />
</div>
@@ -293,7 +293,7 @@ export const ShowProjects = () => {
<div
onClick={(e) => e.stopPropagation()}
>
{(auth?.rol === "admin" ||
{(auth?.role === "admin" ||
user?.canDeleteProjects) && (
<AlertDialog>
<AlertDialogTrigger className="w-full">

View File

@@ -28,6 +28,7 @@ import { BookIcon, CircuitBoard, GlobeIcon } from "lucide-react";
import { useRouter } from "next/router";
import React from "react";
import { StatusTooltip } from "../shared/status-tooltip";
import { authClient } from "@/lib/auth";
type Project = Awaited<ReturnType<typeof findProjectById>>;
@@ -35,8 +36,10 @@ export const SearchCommand = () => {
const router = useRouter();
const [open, setOpen] = React.useState(false);
const [search, setSearch] = React.useState("");
const { data } = api.project.all.useQuery();
const { data: session } = authClient.getSession();
const { data } = api.project.all.useQuery(undefined, {
enabled: !!session,
});
const { data: isCloud, isLoading } = api.settings.isCloud.useQuery();
React.useEffect(() => {

View File

@@ -155,7 +155,7 @@ const MENU: Menu = {
// Only enabled for admins and users with access to Traefik files in non-cloud environments
isEnabled: ({ auth, user, isCloud }) =>
!!(
(auth?.rol === "admin" || user?.canAccessToTraefikFiles) &&
(auth?.role === "admin" || user?.canAccessToTraefikFiles) &&
!isCloud
),
},
@@ -166,7 +166,7 @@ const MENU: Menu = {
icon: BlocksIcon,
// Only enabled for admins and users with access to Docker in non-cloud environments
isEnabled: ({ auth, user, isCloud }) =>
!!((auth?.rol === "admin" || user?.canAccessToDocker) && !isCloud),
!!((auth?.role === "admin" || user?.canAccessToDocker) && !isCloud),
},
{
isSingle: true,
@@ -175,7 +175,7 @@ const MENU: Menu = {
icon: PieChart,
// Only enabled for admins and users with access to Docker in non-cloud environments
isEnabled: ({ auth, user, isCloud }) =>
!!((auth?.rol === "admin" || user?.canAccessToDocker) && !isCloud),
!!((auth?.role === "admin" || user?.canAccessToDocker) && !isCloud),
},
{
isSingle: true,
@@ -184,7 +184,7 @@ const MENU: Menu = {
icon: Forward,
// Only enabled for admins and users with access to Docker in non-cloud environments
isEnabled: ({ auth, user, isCloud }) =>
!!((auth?.rol === "admin" || user?.canAccessToDocker) && !isCloud),
!!((auth?.role === "admin" || user?.canAccessToDocker) && !isCloud),
},
// Legacy unused menu, adjusted to the new structure
@@ -252,7 +252,7 @@ const MENU: Menu = {
icon: Activity,
// Only enabled for admins in non-cloud environments
isEnabled: ({ auth, user, isCloud }) =>
!!(auth?.rol === "admin" && !isCloud),
!!(auth?.role === "admin" && !isCloud),
},
{
isSingle: true,
@@ -266,7 +266,7 @@ const MENU: Menu = {
url: "/dashboard/settings/servers",
icon: Server,
// Only enabled for admins
isEnabled: ({ auth, user, isCloud }) => !!(auth?.rol === "admin"),
isEnabled: ({ auth, user, isCloud }) => !!(auth?.role === "admin"),
},
{
isSingle: true,
@@ -274,7 +274,7 @@ const MENU: Menu = {
icon: Users,
url: "/dashboard/settings/users",
// Only enabled for admins
isEnabled: ({ auth, user, isCloud }) => !!(auth?.rol === "admin"),
isEnabled: ({ auth, user, isCloud }) => !!(auth?.role === "admin"),
},
{
isSingle: true,
@@ -283,7 +283,7 @@ const MENU: Menu = {
url: "/dashboard/settings/ssh-keys",
// Only enabled for admins and users with access to SSH keys
isEnabled: ({ auth, user }) =>
!!(auth?.rol === "admin" || user?.canAccessToSSHKeys),
!!(auth?.role === "admin" || user?.canAccessToSSHKeys),
},
{
isSingle: true,
@@ -292,7 +292,7 @@ const MENU: Menu = {
icon: GitBranch,
// Only enabled for admins and users with access to Git providers
isEnabled: ({ auth, user }) =>
!!(auth?.rol === "admin" || user?.canAccessToGitProviders),
!!(auth?.role === "admin" || user?.canAccessToGitProviders),
},
{
isSingle: true,
@@ -300,7 +300,7 @@ const MENU: Menu = {
url: "/dashboard/settings/registry",
icon: Package,
// Only enabled for admins
isEnabled: ({ auth, user, isCloud }) => !!(auth?.rol === "admin"),
isEnabled: ({ auth, user, isCloud }) => !!(auth?.role === "admin"),
},
{
isSingle: true,
@@ -308,7 +308,7 @@ const MENU: Menu = {
url: "/dashboard/settings/destinations",
icon: Database,
// Only enabled for admins
isEnabled: ({ auth, user, isCloud }) => !!(auth?.rol === "admin"),
isEnabled: ({ auth, user, isCloud }) => !!(auth?.role === "admin"),
},
{
@@ -317,7 +317,7 @@ const MENU: Menu = {
url: "/dashboard/settings/certificates",
icon: ShieldCheck,
// Only enabled for admins
isEnabled: ({ auth, user, isCloud }) => !!(auth?.rol === "admin"),
isEnabled: ({ auth, user, isCloud }) => !!(auth?.role === "admin"),
},
{
isSingle: true,
@@ -326,7 +326,7 @@ const MENU: Menu = {
icon: Boxes,
// Only enabled for admins in non-cloud environments
isEnabled: ({ auth, user, isCloud }) =>
!!(auth?.rol === "admin" && !isCloud),
!!(auth?.role === "admin" && !isCloud),
},
{
isSingle: true,
@@ -334,7 +334,7 @@ const MENU: Menu = {
url: "/dashboard/settings/notifications",
icon: Bell,
// Only enabled for admins
isEnabled: ({ auth, user, isCloud }) => !!(auth?.rol === "admin"),
isEnabled: ({ auth, user, isCloud }) => !!(auth?.role === "admin"),
},
{
isSingle: true,
@@ -343,7 +343,7 @@ const MENU: Menu = {
icon: CreditCard,
// Only enabled for admins in cloud environments
isEnabled: ({ auth, user, isCloud }) =>
!!(auth?.rol === "admin" && isCloud),
!!(auth?.role === "admin" && isCloud),
},
],
@@ -537,7 +537,7 @@ export default function Page({ children }: Props) {
authId: auth?.id || "",
},
{
enabled: !!auth?.id && auth?.rol === "user",
enabled: !!auth?.id && auth?.role === "user",
},
);
@@ -783,7 +783,7 @@ export default function Page({ children }: Props) {
</SidebarMenuButton>
</SidebarMenuItem>
))}
{!isCloud && auth?.rol === "admin" && (
{!isCloud && auth?.role === "admin" && (
<SidebarMenuItem>
<SidebarMenuButton asChild>
<UpdateServerButton />

View File

@@ -24,6 +24,7 @@ import { useRouter } from "next/router";
import { useEffect, useRef, useState } from "react";
import { ModeToggle } from "../ui/modeToggle";
import { SidebarMenuButton } from "../ui/sidebar";
import { authClient } from "@/lib/auth";
const AUTO_CHECK_UPDATES_INTERVAL_MINUTES = 7;
@@ -40,7 +41,7 @@ export const UserNav = () => {
},
);
const { locale, setLocale } = useLocale();
const { mutateAsync } = api.auth.logout.useMutation();
// const { mutateAsync } = api.auth.logout.useMutation();
return (
<DropdownMenu>
@@ -178,9 +179,12 @@ export const UserNav = () => {
<DropdownMenuItem
className="cursor-pointer"
onClick={async () => {
await mutateAsync().then(() => {
await authClient.signOut().then(() => {
router.push("/");
});
// await mutateAsync().then(() => {
// router.push("/");
// });
}}
>
Log out

View File

@@ -1,4 +1,7 @@
import { createAuthClient } from "better-auth/react";
import { organizationClient } from "better-auth/client/plugins";
export const authClient = createAuthClient({
baseURL: "http://localhost:3000", // the base url of your auth server
plugins: [organizationClient()],
});

View File

@@ -42,7 +42,7 @@ export default async function handler(
const auth = await findAuthById(value as string);
let adminId = "";
if (auth.rol === "admin") {
if (auth.role === "admin") {
const admin = await findAdminByAuthId(auth.id);
adminId = admin.adminId;
} else {

View File

@@ -53,7 +53,7 @@ export async function getServerSideProps(
await helpers.project.all.prefetch();
const auth = await helpers.auth.get.fetch();
if (auth.rol === "user") {
if (auth.role === "user") {
const user = await helpers.user.byAuthId.fetch({
authId: auth.id,
});

View File

@@ -206,7 +206,7 @@ const Project = (
authId: auth?.id || "",
},
{
enabled: !!auth?.id && auth?.rol === "user",
enabled: !!auth?.id && auth?.role === "user",
},
);
const { data, isLoading, refetch } = api.project.one.useQuery({ projectId });
@@ -335,7 +335,7 @@ const Project = (
</CardTitle>
<CardDescription>{data?.description}</CardDescription>
</CardHeader>
{(auth?.rol === "admin" || user?.canCreateServices) && (
{(auth?.role === "admin" || user?.canCreateServices) && (
<div className="flex flex-row gap-4 flex-wrap">
<ProjectEnvironment projectId={projectId}>
<Button variant="outline">Project Environment</Button>

View File

@@ -93,7 +93,7 @@ const Service = (
authId: auth?.id || "",
},
{
enabled: !!auth?.id && auth?.rol === "user",
enabled: !!auth?.id && auth?.role === "user",
},
);
@@ -186,7 +186,7 @@ const Service = (
<div className="flex flex-row gap-2 justify-end">
<UpdateApplication applicationId={applicationId} />
{(auth?.rol === "admin" || user?.canDeleteServices) && (
{(auth?.role === "admin" || user?.canDeleteServices) && (
<DeleteService id={applicationId} type="application" />
)}
</div>

View File

@@ -87,7 +87,7 @@ const Service = (
authId: auth?.id || "",
},
{
enabled: !!auth?.id && auth?.rol === "user",
enabled: !!auth?.id && auth?.role === "user",
},
);
@@ -181,7 +181,7 @@ const Service = (
<div className="flex flex-row gap-2 justify-end">
<UpdateCompose composeId={composeId} />
{(auth?.rol === "admin" || user?.canDeleteServices) && (
{(auth?.role === "admin" || user?.canDeleteServices) && (
<DeleteService id={composeId} type="compose" />
)}
</div>

View File

@@ -68,7 +68,7 @@ const Mariadb = (
authId: auth?.id || "",
},
{
enabled: !!auth?.id && auth?.rol === "user",
enabled: !!auth?.id && auth?.role === "user",
},
);
const { data: isCloud } = api.settings.isCloud.useQuery();
@@ -154,7 +154,7 @@ const Mariadb = (
</div>
<div className="flex flex-row gap-2 justify-end">
<UpdateMariadb mariadbId={mariadbId} />
{(auth?.rol === "admin" || user?.canDeleteServices) && (
{(auth?.role === "admin" || user?.canDeleteServices) && (
<DeleteService id={mariadbId} type="mariadb" />
)}
</div>

View File

@@ -68,7 +68,7 @@ const Mongo = (
authId: auth?.id || "",
},
{
enabled: !!auth?.id && auth?.rol === "user",
enabled: !!auth?.id && auth?.role === "user",
},
);
@@ -156,7 +156,7 @@ const Mongo = (
<div className="flex flex-row gap-2 justify-end">
<UpdateMongo mongoId={mongoId} />
{(auth?.rol === "admin" || user?.canDeleteServices) && (
{(auth?.role === "admin" || user?.canDeleteServices) && (
<DeleteService id={mongoId} type="mongo" />
)}
</div>

View File

@@ -67,7 +67,7 @@ const MySql = (
authId: auth?.id || "",
},
{
enabled: !!auth?.id && auth?.rol === "user",
enabled: !!auth?.id && auth?.role === "user",
},
);
@@ -156,7 +156,7 @@ const MySql = (
<div className="flex flex-row gap-2 justify-end">
<UpdateMysql mysqlId={mysqlId} />
{(auth?.rol === "admin" || user?.canDeleteServices) && (
{(auth?.role === "admin" || user?.canDeleteServices) && (
<DeleteService id={mysqlId} type="mysql" />
)}
</div>

View File

@@ -66,7 +66,7 @@ const Postgresql = (
authId: auth?.id || "",
},
{
enabled: !!auth?.id && auth?.rol === "user",
enabled: !!auth?.id && auth?.role === "user",
},
);
const { data: monitoring } = api.admin.getMetricsToken.useQuery();
@@ -154,7 +154,7 @@ const Postgresql = (
<div className="flex flex-row gap-2 justify-end">
<UpdatePostgres postgresId={postgresId} />
{(auth?.rol === "admin" || user?.canDeleteServices) && (
{(auth?.role === "admin" || user?.canDeleteServices) && (
<DeleteService id={postgresId} type="postgres" />
)}
</div>

View File

@@ -67,7 +67,7 @@ const Redis = (
authId: auth?.id || "",
},
{
enabled: !!auth?.id && auth?.rol === "user",
enabled: !!auth?.id && auth?.role === "user",
},
);
@@ -155,7 +155,7 @@ const Redis = (
<div className="flex flex-row gap-2 justify-end">
<UpdateRedis redisId={redisId} />
{(auth?.rol === "admin" || user?.canDeleteServices) && (
{(auth?.role === "admin" || user?.canDeleteServices) && (
<DeleteService id={redisId} type="redis" />
)}
</div>

View File

@@ -30,7 +30,7 @@ export async function getServerSideProps(
}
const { req, res } = ctx;
const { user, session } = await validateRequest(req, res);
if (!user || user.rol === "user") {
if (!user || user.role === "user") {
return {
redirect: {
permanent: true,

View File

@@ -25,7 +25,7 @@ export async function getServerSideProps(
) {
const { req, res } = ctx;
const { user, session } = await validateRequest(req, res);
if (!user || user.rol === "user") {
if (!user || user.role === "user") {
return {
redirect: {
permanent: true,

View File

@@ -34,7 +34,7 @@ export async function getServerSideProps(
};
}
const { user, session } = await validateRequest(ctx.req, ctx.res);
if (!user || user.rol === "user") {
if (!user || user.role === "user") {
return {
redirect: {
permanent: true,

View File

@@ -26,7 +26,7 @@ export async function getServerSideProps(
) {
const { req, res } = ctx;
const { user, session } = await validateRequest(req, res);
if (!user || user.rol === "user") {
if (!user || user.role === "user") {
return {
redirect: {
permanent: true,

View File

@@ -51,7 +51,7 @@ export async function getServerSideProps(
await helpers.settings.isCloud.prefetch();
const auth = await helpers.auth.get.fetch();
if (auth.rol === "user") {
if (auth.role === "user") {
const user = await helpers.user.byAuthId.fetch({
authId: auth.id,
});

View File

@@ -190,7 +190,7 @@ export async function getServerSideProps(
},
};
}
if (user.rol === "user") {
if (user.role === "user") {
return {
redirect: {
permanent: true,

View File

@@ -26,7 +26,7 @@ export async function getServerSideProps(
) {
const { req, res } = ctx;
const { user, session } = await validateRequest(req, res);
if (!user || user.rol === "user") {
if (!user || user.role === "user") {
return {
redirect: {
permanent: true,

View File

@@ -19,7 +19,7 @@ const Page = () => {
authId: data?.id || "",
},
{
enabled: !!data?.id && data?.rol === "user",
enabled: !!data?.id && data?.role === "user",
},
);
@@ -28,7 +28,7 @@ const Page = () => {
<div className="w-full">
<div className="h-full rounded-xl max-w-5xl mx-auto flex flex-col gap-4">
<ProfileForm />
{(user?.canAccessToAPI || data?.rol === "admin") && <GenerateToken />}
{(user?.canAccessToAPI || data?.role === "admin") && <GenerateToken />}
{isCloud && <RemoveSelfAccount />}
</div>
@@ -62,7 +62,7 @@ export async function getServerSideProps(
await helpers.settings.isCloud.prefetch();
await helpers.auth.get.prefetch();
if (user?.rol === "user") {
if (user?.role === "user") {
await helpers.user.byAuthId.prefetch({
authId: user.authId,
});

View File

@@ -26,7 +26,7 @@ export async function getServerSideProps(
) {
const { req, res } = ctx;
const { user, session } = await validateRequest(req, res);
if (!user || user.rol === "user") {
if (!user || user.role === "user") {
return {
redirect: {
permanent: true,

View File

@@ -107,7 +107,7 @@ export async function getServerSideProps(
},
};
}
if (user.rol === "user") {
if (user.role === "user") {
return {
redirect: {
permanent: true,

View File

@@ -36,7 +36,7 @@ export async function getServerSideProps(
},
};
}
if (user.rol === "user") {
if (user.role === "user") {
return {
redirect: {
permanent: true,

View File

@@ -51,7 +51,7 @@ export async function getServerSideProps(
const auth = await helpers.auth.get.fetch();
await helpers.settings.isCloud.prefetch();
if (auth.rol === "user") {
if (auth.role === "user") {
const user = await helpers.user.byAuthId.fetch({
authId: auth.id,
});

View File

@@ -26,7 +26,7 @@ export async function getServerSideProps(
) {
const { req, res } = ctx;
const { user, session } = await validateRequest(req, res);
if (!user || user.rol === "user") {
if (!user || user.role === "user") {
return {
redirect: {
permanent: true,

View File

@@ -53,7 +53,7 @@ export async function getServerSideProps(
await helpers.project.all.prefetch();
const auth = await helpers.auth.get.fetch();
if (auth.rol === "user") {
if (auth.role === "user") {
const user = await helpers.user.byAuthId.fetch({
authId: auth.id,
});

View File

@@ -53,7 +53,7 @@ export async function getServerSideProps(
await helpers.project.all.prefetch();
const auth = await helpers.auth.get.fetch();
if (auth.rol === "user") {
if (auth.role === "user") {
const user = await helpers.user.byAuthId.fetch({
authId: auth.id,
});

View File

@@ -16,8 +16,11 @@ import { Input } from "@/components/ui/input";
import { authClient } from "@/lib/auth";
import { cn } from "@/lib/utils";
import { api } from "@/utils/api";
import { IS_CLOUD, isAdminPresent, validateRequest } from "@dokploy/server";
import { auth, IS_CLOUD, isAdminPresent } from "@dokploy/server";
import { validateRequest } from "@dokploy/server/lib/auth";
import { zodResolver } from "@hookform/resolvers/zod";
import { getSessionCookie, Session } from "better-auth";
import { betterFetch } from "better-auth/react";
import type { GetServerSidePropsContext } from "next";
import Link from "next/link";
import { useRouter } from "next/router";
@@ -66,7 +69,7 @@ export default function Home({ IS_CLOUD }: Props) {
const router = useRouter();
const form = useForm<Login>({
defaultValues: {
email: "siumauricio@hotmail.com",
email: "user5@yopmail.com",
password: "Password1234",
},
resolver: zodResolver(loginSchema),
@@ -82,6 +85,17 @@ export default function Home({ IS_CLOUD }: Props) {
password: values.password,
});
if (!error) {
// if (data) {
// setTemp(data);
// } else {
toast.success("Successfully signed in", {
duration: 2000,
});
// router.push("/dashboard/projects");
// }
}
console.log(data, error);
// await mutateAsync({
// email: values.email.toLowerCase(),
@@ -208,51 +222,51 @@ Home.getLayout = (page: ReactElement) => {
return <OnboardingLayout>{page}</OnboardingLayout>;
};
export async function getServerSideProps(context: GetServerSidePropsContext) {
// if (IS_CLOUD) {
// try {
// const { user } = await validateRequest(context.req, context.res);
// if (user) {
// return {
// redirect: {
// permanent: true,
// destination: "/dashboard/projects",
// },
// };
// }
// } catch (error) {}
// return {
// props: {
// IS_CLOUD: IS_CLOUD,
// },
// };
// }
// const hasAdmin = await isAdminPresent();
// if (!hasAdmin) {
// return {
// redirect: {
// permanent: true,
// destination: "/register",
// },
// };
// }
// const { user } = await validateRequest(context.req, context.res);
// if (user) {
// return {
// redirect: {
// permanent: true,
// destination: "/dashboard/projects",
// },
// };
// }
if (IS_CLOUD) {
try {
const { user } = await validateRequest(context.req);
if (user) {
return {
redirect: {
permanent: true,
destination: "/dashboard/projects",
},
};
}
} catch (error) {}
return {
props: {
// hasAdmin,
IS_CLOUD: IS_CLOUD,
},
};
}
const hasAdmin = await isAdminPresent();
if (!hasAdmin) {
return {
redirect: {
permanent: true,
destination: "/register",
},
};
}
const { user } = await validateRequest(context.req);
console.log("Response", user);
if (user) {
return {
redirect: {
permanent: true,
destination: "/dashboard/projects",
},
};
}
return {
props: {
hasAdmin,
},
};
}

View File

@@ -32,6 +32,9 @@ import { z } from "zod";
const registerSchema = z
.object({
name: z.string().min(1, {
message: "Name is required",
}),
email: z
.string()
.min(1, {
@@ -80,6 +83,7 @@ const Register = ({ isCloud }: Props) => {
const form = useForm<Register>({
defaultValues: {
name: "Mauricio Siu",
email: "user5@yopmail.com",
password: "Password1234",
confirmPassword: "Password1234",
@@ -95,8 +99,17 @@ const Register = ({ isCloud }: Props) => {
const { data, error } = await authClient.signUp.email({
email: values.email,
password: values.password,
name: "Mauricio Siu",
name: values.name,
});
// const { data, error } = await authClient.admin.createUser({
// name: values.name,
// email: values.email,
// password: values.password,
// role: "superAdmin",
// });
// consol/e.log(data, error);
// await mutateAsync({
// email: values.email.toLowerCase(),
// password: values.password,
@@ -153,6 +166,19 @@ const Register = ({ isCloud }: Props) => {
className="grid gap-4"
>
<div className="space-y-4">
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Name</FormLabel>
<FormControl>
<Input placeholder="name" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="email"

View File

@@ -58,7 +58,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
},
transformer: superjson,
});
if (user.rol === "user") {
if (user.role === "user") {
const result = await helpers.user.byAuthId.fetch({
authId: user.id,
});

View File

@@ -169,7 +169,8 @@ export const authRouter = createTRPCRouter({
}),
get: protectedProcedure.query(async ({ ctx }) => {
const auth = await findAuthById(ctx.user.authId);
console.log(ctx.user);
const auth = await findAuthById(ctx.user.id);
return auth;
}),

View File

@@ -124,9 +124,10 @@ export const projectRouter = createTRPCRouter({
return project;
}),
all: protectedProcedure.query(async ({ ctx }) => {
// console.log(ctx.user);
if (ctx.user.rol === "user") {
const { accessedProjects, accessedServices } = await findUserByAuthId(
ctx.user.authId,
ctx.user.id,
);
if (accessedProjects.length === 0) {
@@ -139,7 +140,7 @@ export const projectRouter = createTRPCRouter({
accessedProjects.map((projectId) => sql`${projectId}`),
sql`, `,
)})`,
eq(projects.adminId, ctx.user.adminId),
eq(projects.userId, ctx.user.id),
),
with: {
applications: {
@@ -193,7 +194,7 @@ export const projectRouter = createTRPCRouter({
},
},
},
where: eq(projects.adminId, ctx.user.adminId),
where: eq(projects.userId, ctx.user.id),
orderBy: desc(projects.createdAt),
});
}),

View File

@@ -655,7 +655,7 @@ export const settingsRouter = createTRPCRouter({
return true;
}),
isCloud: protectedProcedure.query(async () => {
isCloud: publicProcedure.query(async () => {
return IS_CLOUD;
}),
health: publicProcedure.query(async () => {

View File

@@ -9,7 +9,8 @@
// import { getServerAuthSession } from "@/server/auth";
import { db } from "@/server/db";
import { validateBearerToken, validateRequest } from "@dokploy/server";
import { validateBearerToken } from "@dokploy/server";
import { validateRequest } from "@dokploy/server/lib/auth";
import type { OpenApiMeta } from "@dokploy/trpc-openapi";
import { TRPCError, initTRPC } from "@trpc/server";
import type { CreateNextContextOptions } from "@trpc/server/adapters/next";
@@ -18,7 +19,7 @@ import {
experimental_isMultipartFormDataRequest,
experimental_parseMultipartFormData,
} from "@trpc/server/adapters/node-http/content-type/form-data";
import type { Session, User } from "lucia";
import type { Session, User } from "better-auth";
import superjson from "superjson";
import { ZodError } from "zod";
/**
@@ -30,7 +31,7 @@ import { ZodError } from "zod";
*/
interface CreateContextOptions {
user: (User & { authId: string; adminId: string }) | null;
user: (User & { authId: string; rol: "admin" | "user" }) | null;
session: Session | null;
req: CreateNextContextOptions["req"];
res: CreateNextContextOptions["res"];
@@ -65,10 +66,11 @@ const createInnerTRPCContext = (opts: CreateContextOptions) => {
export const createTRPCContext = async (opts: CreateNextContextOptions) => {
const { req, res } = opts;
let { session, user } = await validateBearerToken(req);
// Get from the request
let { session, user } = await validateRequest(req);
if (!session) {
const cookieResult = await validateRequest(req, res);
const cookieResult = await validateRequest(req);
session = cookieResult.session;
user = cookieResult.user;
}
@@ -79,12 +81,10 @@ export const createTRPCContext = async (opts: CreateNextContextOptions) => {
session: session,
...((user && {
user: {
authId: user.id,
authId: "Null",
email: user.email,
rol: user.rol,
rol: user.role,
id: user.id,
secret: user.secret,
adminId: user.adminId,
},
}) || {
user: null,

View File

@@ -1,9 +1,9 @@
import {
boolean,
integer,
pgTable,
text,
integer,
timestamp,
boolean,
} from "drizzle-orm/pg-core";
export const user = pgTable("user", {
@@ -14,10 +14,6 @@ export const user = pgTable("user", {
image: text("image"),
createdAt: timestamp("created_at").notNull(),
updatedAt: timestamp("updated_at").notNull(),
role: text("role"),
banned: boolean("banned"),
banReason: text("ban_reason"),
banExpires: timestamp("ban_expires"),
});
export const session = pgTable("session", {
@@ -31,32 +27,67 @@ export const session = pgTable("session", {
userId: text("user_id")
.notNull()
.references(() => user.id),
impersonatedBy: text("impersonated_by"),
activeOrganizationId: text("active_organization_id"),
});
// export const account = pgTable("account", {
// id: text("id").primaryKey(),
// accountId: text("account_id").notNull(),
// providerId: text("provider_id").notNull(),
// userId: text("user_id")
// .notNull()
// .references(() => user.id),
// accessToken: text("access_token"),
// refreshToken: text("refresh_token"),
// idToken: text("id_token"),
// accessTokenExpiresAt: timestamp("access_token_expires_at"),
// refreshTokenExpiresAt: timestamp("refresh_token_expires_at"),
// scope: text("scope"),
// password: text("password"),
// createdAt: timestamp("created_at").notNull(),
// updatedAt: timestamp("updated_at").notNull(),
// });
export const account = pgTable("account", {
id: text("id").primaryKey(),
accountId: text("account_id").notNull(),
providerId: text("provider_id").notNull(),
userId: text("user_id")
.notNull()
.references(() => user.id),
accessToken: text("access_token"),
refreshToken: text("refresh_token"),
idToken: text("id_token"),
accessTokenExpiresAt: timestamp("access_token_expires_at"),
refreshTokenExpiresAt: timestamp("refresh_token_expires_at"),
scope: text("scope"),
password: text("password"),
createdAt: timestamp("created_at").notNull(),
updatedAt: timestamp("updated_at").notNull(),
});
// export const verification = pgTable("verification", {
// id: text("id").primaryKey(),
// identifier: text("identifier").notNull(),
// value: text("value").notNull(),
// expiresAt: timestamp("expires_at").notNull(),
// createdAt: timestamp("created_at"),
// updatedAt: timestamp("updated_at"),
// });
export const verification = pgTable("verification", {
id: text("id").primaryKey(),
identifier: text("identifier").notNull(),
value: text("value").notNull(),
expiresAt: timestamp("expires_at").notNull(),
createdAt: timestamp("created_at"),
updatedAt: timestamp("updated_at"),
});
export const organization = pgTable("organization", {
id: text("id").primaryKey(),
name: text("name").notNull(),
slug: text("slug").unique(),
logo: text("logo"),
createdAt: timestamp("created_at").notNull(),
metadata: text("metadata"),
});
export const member = pgTable("member", {
id: text("id").primaryKey(),
organizationId: text("organization_id")
.notNull()
.references(() => organization.id),
userId: text("user_id")
.notNull()
.references(() => user.id),
role: text("role").notNull(),
createdAt: timestamp("created_at").notNull(),
});
export const invitation = pgTable("invitation", {
id: text("id").primaryKey(),
organizationId: text("organization_id")
.notNull()
.references(() => organization.id),
email: text("email").notNull(),
role: text("role"),
status: text("status").notNull(),
expiresAt: timestamp("expires_at").notNull(),
inviterId: text("inviter_id")
.notNull()
.references(() => user.id),
});

View File

@@ -6,9 +6,9 @@ import { TimeSpan } from "lucia";
import { Lucia } from "lucia/dist/core.js";
import type { Session, User } from "lucia/dist/core.js";
import { db } from "../db";
import { type DatabaseUser, auth, sessionTable } from "../db/schema";
import { type DatabaseUser, auth, session } from "../db/schema";
export const adapter = new DrizzlePostgreSQLAdapter(db, sessionTable, auth);
export const adapter = new DrizzlePostgreSQLAdapter(db, session, auth);
export const lucia = new Lucia(adapter, {
sessionCookie: {
@@ -46,6 +46,7 @@ export async function validateRequest(
req: IncomingMessage,
res: ServerResponse,
): ReturnValidateToken {
console.log(session);
const sessionId = lucia.readSessionCookie(req.headers.cookie ?? "");
if (!sessionId) {

View File

@@ -32,3 +32,38 @@ export const verification = pgTable("verification", {
createdAt: timestamp("created_at"),
updatedAt: timestamp("updated_at"),
});
export const organization = pgTable("organization", {
id: text("id").primaryKey(),
name: text("name").notNull(),
slug: text("slug").unique(),
logo: text("logo"),
createdAt: timestamp("created_at").notNull(),
metadata: text("metadata"),
});
export const member = pgTable("member", {
id: text("id").primaryKey(),
organizationId: text("organization_id")
.notNull()
.references(() => organization.id),
userId: text("user_id")
.notNull()
.references(() => user.id),
role: text("role").notNull(),
createdAt: timestamp("created_at").notNull(),
});
export const invitation = pgTable("invitation", {
id: text("id").primaryKey(),
organizationId: text("organization_id")
.notNull()
.references(() => organization.id),
email: text("email").notNull(),
role: text("role"),
status: text("status").notNull(),
expiresAt: timestamp("expires_at").notNull(),
inviterId: text("inviter_id")
.notNull()
.references(() => user.id),
});

View File

@@ -2,7 +2,7 @@ import { pgTable, text, timestamp } from "drizzle-orm/pg-core";
import { user } from "./user";
// OLD TABLE
export const sessionTable = pgTable("session", {
export const session = pgTable("session", {
id: text("id").primaryKey(),
expiresAt: timestamp("expires_at").notNull(),
token: text("token").notNull().unique(),
@@ -14,4 +14,5 @@ export const sessionTable = pgTable("session", {
.notNull()
.references(() => user.id),
impersonatedBy: text("impersonated_by"),
activeOrganizationId: text("active_organization_id"),
});

View File

@@ -24,11 +24,16 @@ export const user = pgTable("user", {
isRegistered: boolean("isRegistered").notNull().default(false),
expirationDate: timestamp("expirationDate", {
precision: 3,
mode: "string",
}).notNull(),
createdAt: text("createdAt")
mode: "date",
})
.notNull()
.$defaultFn(() => new Date().toISOString()),
.$defaultFn(() => new Date()),
createdAt: timestamp("createdAt", {
precision: 3,
mode: "date",
})
.notNull()
.$defaultFn(() => new Date()),
canCreateProjects: boolean("canCreateProjects").notNull().default(false),
canAccessToSSHKeys: boolean("canAccessToSSHKeys").notNull().default(false),
canCreateServices: boolean("canCreateServices").notNull().default(false),

View File

@@ -1,8 +1,10 @@
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { admin } from "better-auth/plugins";
import { admin, createAuthMiddleware, organization } from "better-auth/plugins";
import { db } from "../db";
import * as schema from "../db/schema";
import type { IncomingMessage } from "node:http";
import { eq } from "drizzle-orm";
export const auth = betterAuth({
database: drizzleAdapter(db, {
provider: "pg",
@@ -11,5 +13,38 @@ export const auth = betterAuth({
emailAndPassword: {
enabled: true,
},
plugins: [admin()],
hooks: {
after: createAuthMiddleware(async (ctx) => {
if (ctx.path.startsWith("/sign-up")) {
const newSession = ctx.context.newSession;
await db
.update(schema.user)
.set({
role: "admin",
})
.where(eq(schema.user.id, newSession?.user?.id || ""));
}
}),
},
user: {
additionalFields: {},
},
plugins: [organization()],
});
export const validateRequest = async (request: IncomingMessage) => {
const session = await auth.api.getSession({
headers: new Headers({
cookie: request.headers.cookie || "",
}),
});
if (!session?.session || !session.user) {
return {
session: null,
user: null,
};
}
return session;
};

View File

@@ -94,8 +94,8 @@ export const updateAdminById = async (
};
export const isAdminPresent = async () => {
const admin = await db.query.users.findFirst({
where: eq(users.role, "admin"),
const admin = await db.query.user.findFirst({
where: eq(user.role, "admin"),
});
if (!admin) {
return false;

View File

@@ -100,10 +100,11 @@ export const findAuthByEmail = async (email: string) => {
};
export const findAuthById = async (authId: string) => {
const result = await db.query.auth.findFirst({
where: eq(auth.id, authId),
const result = await db.query.user.findFirst({
where: eq(user.id, authId),
columns: {
password: false,
createdAt: false,
updatedAt: false,
},
});
if (!result) {

View File

@@ -20,18 +20,16 @@ export const findUserById = async (userId: string) => {
export const findUserByAuthId = async (authId: string) => {
const userR = await db.query.user.findFirst({
where: eq(user.authId, authId),
with: {
auth: true,
},
where: eq(user.id, authId),
with: {},
});
if (!user) {
if (!userR) {
throw new TRPCError({
code: "NOT_FOUND",
message: "User not found",
});
}
return user;
return userR;
};
export const findUsers = async (adminId: string) => {