refactor(users): some user refactoring and configuring

This commit is contained in:
2026-05-13 16:42:36 -05:00
parent b0c7277a6c
commit 342a97f6b1
7 changed files with 288 additions and 9 deletions

View File

@@ -0,0 +1,46 @@
/**
* To be able to run this we need to set our dev pc in the .env.
* if its empty just ignore it. this will just be the double catch
*/
import { fromNodeHeaders } from "better-auth/node";
import { Router } from "express";
import { auth } from "../utils/auth.utils.js";
import { apiReturn } from "../utils/returnHelper.utils.js";
const r = Router();
r.get("/", async (req, res) => {
const { users } = await auth.api.listUsers({
query: {
limit: 50,
},
headers: fromNodeHeaders(req.headers),
});
// console.log(error);
// if (error) {
// return apiReturn(res, {
// success: false,
// level: "info",
// module: "admin",
// subModule: "user",
// message: `There was an error getting the users.`,
// data: users,
// status: 400,
// });
// }
return apiReturn(res, {
success: true,
level: "info",
module: "admin",
subModule: "users",
message: `Current active users.`,
data: users,
status: 200,
});
});
export default r;

View File

@@ -1,8 +1,9 @@
import { createAccessControl } from "better-auth/plugins/access";
import { adminAc } from "better-auth/plugins/admin/access";
export const statement = {
app: ["read", "create", "share", "update", "delete", "readAll"],
user: ["ban"],
//user: ["ban"],
quality: ["read", "create", "share", "update", "delete", "readAll"],
notifications: ["read", "create", "share", "update", "delete", "readAll"],
} as const;
@@ -20,7 +21,8 @@ export const admin = ac.newRole({
export const systemAdmin = ac.newRole({
app: ["read", "create", "share", "update", "delete", "readAll"],
user: ["ban"],
//user: ["ban"],
quality: ["read", "create", "share", "update", "delete", "readAll"],
notifications: ["read", "create", "share", "update", "delete", "readAll"],
...adminAc.statements,
});

View File

@@ -1,6 +1,6 @@
import { adminClient, genericOAuthClient } from "better-auth/client/plugins";
import { createAuthClient } from "better-auth/react";
import { ac, admin, systemAdmin, user } from "./auth-permissions";
import { ac, admin, manager, systemAdmin, user } from "./auth-permissions";
export const authClient = createAuthClient({
baseURL: `${window.location.origin}/lst/api/auth`,
@@ -10,6 +10,7 @@ export const authClient = createAuthClient({
roles: {
admin,
user,
manager,
systemAdmin,
},
}),

View File

@@ -1,21 +1,53 @@
import { createAccessControl } from "better-auth/plugins/access";
import { adminAc } from "better-auth/plugins/admin/access";
export const statement = {
project: ["create", "share", "update", "delete"],
user: ["ban"],
app: ["read", "create", "share", "update", "delete", "readAll"],
//user: ["ban"],
quality: ["read", "create", "share", "update", "delete", "readAll"],
logistics: ["read", "create", "share", "update", "delete", "readAll"],
mobile: ["read", "create", "share", "update", "delete", "readAll"],
notifications: ["read", "create", "share", "update", "delete", "readAll"],
} as const;
export const ac = createAccessControl(statement);
export const user = ac.newRole({
project: ["create"],
app: ["read", "create"],
notifications: ["read", "create"],
});
export const manager = ac.newRole({
app: ["read", "create", "update"],
});
export const admin = ac.newRole({
project: ["create", "update"],
app: ["read", "create", "update"],
});
export const systemAdmin = ac.newRole({
project: ["create", "update", "delete"],
user: ["ban"],
app: ["read", "create", "share", "update", "delete", "readAll"],
//user: ["ban"],
quality: ["read", "create", "share", "update", "delete", "readAll"],
mobile: ["read", "create", "share", "update", "delete", "readAll"],
logistics: ["read", "create", "share", "update", "delete", "readAll"],
notifications: ["read", "create", "share", "update", "delete", "readAll"],
...adminAc.statements,
});
/* example usage
const canCreateProject = await authClient.admin.hasPermission({
permissions: {
project: ["create"],
},
});
// You can also check multiple resource permissions at the same time
const canCreateProjectAndCreateSale = await authClient.admin.hasPermission({
permissions: {
project: ["create"],
sale: ["create"]
},
});
*/

View File

@@ -0,0 +1,40 @@
import { keepPreviousData, queryOptions } from "@tanstack/react-query";
import { authClient } from "../auth-client";
export function getUsers() {
return queryOptions({
queryKey: ["getUsers"],
queryFn: () => fetch(),
staleTime: 5000,
refetchOnWindowFocus: true,
placeholderData: keepPreviousData,
});
}
const fetch = async () => {
if (window.location.hostname === "localhost") {
await new Promise((res) => setTimeout(res, 1500));
}
const { data, error } = await authClient.admin.listUsers({
query: {
// searchValue: "some name",
// searchField: "name",
// searchOperator: "contains",
limit: 100,
offset: 0,
sortBy: "name",
// sortDirection: "desc",
// filterField: "email",
// filterValue: "hello@example.com",
// filterOperator: "eq",
},
});
if (error) {
return error;
}
return data.users;
};

View File

@@ -10,6 +10,7 @@ import {
} from "@/components/ui/card";
import { authClient, useSession } from "@/lib/auth-client";
import { useAppForm } from "@/lib/formSutff";
import { Spinner } from "../../components/ui/spinner";
import ChangePassword from "./-components/ChangePassword";
import NotificationsSubCard from "./-components/NotificationsSubCard";
@@ -37,6 +38,7 @@ export const Route = createFileRoute("/(auth)/user/profile")({
function RouteComponent() {
const { data: session } = useSession();
const form = useAppForm({
defaultValues: {
name: session?.user.name,

View File

@@ -0,0 +1,156 @@
import { useSuspenseQuery } from "@tanstack/react-query";
import { createFileRoute, redirect } from "@tanstack/react-router";
import { createColumnHelper } from "@tanstack/react-table";
import { format } from "date-fns-tz";
import { Suspense } from "react";
import { Button } from "../../components/ui/button";
import { authClient, useSession } from "../../lib/auth-client";
import { getUsers } from "../../lib/queries/getUsers";
import LstTable from "../../lib/tableStuff/LstTable";
import SearchableHeader from "../../lib/tableStuff/SearchableHeader";
import SkellyTable from "../../lib/tableStuff/SkellyTable";
import { trackLstEvent } from "../../lib/umami.utils";
export const Route = createFileRoute("/admin/users")({
beforeLoad: async ({ location }) => {
const { data: session } = await authClient.getSession();
const allowedRole = ["systemAdmin", "admin"];
if (!session?.user) {
throw redirect({
to: "/",
search: {
redirect: location.href,
},
});
}
if (!allowedRole.includes(session.user.role as string)) {
throw redirect({
to: "/",
});
}
return { user: session.user };
},
component: RouteComponent,
});
const UserTable = () => {
const { data } = useSuspenseQuery(getUsers());
const { data: session } = useSession();
const columnHelper = createColumnHelper<any>();
const columns = [
columnHelper.accessor("name", {
header: ({ column }) => (
<SearchableHeader column={column} title="Name" searchable={true} />
),
filterFn: "includesString",
cell: (i) => i.getValue(),
}),
columnHelper.accessor("email", {
header: ({ column }) => (
<SearchableHeader column={column} title="Email" searchable={true} />
),
filterFn: "includesString",
cell: (i) => i.getValue(),
}),
columnHelper.accessor("role", {
header: ({ column }) => (
<SearchableHeader column={column} title="Role" searchable={false} />
),
filterFn: "includesString",
cell: (i) => i.getValue(),
}),
columnHelper.accessor("updatedAt", {
header: ({ column }) => (
<SearchableHeader
column={column}
title="Updated at"
searchable={false}
/>
),
filterFn: "includesString",
cell: (i) => format(i.getValue(), "M/d/yyyy HH:mm"),
}),
];
if (session && session.user.role === "systemAdmin") {
columns.push(
columnHelper.accessor("banned", {
header: ({ column }) => (
<SearchableHeader column={column} title="Banned" searchable={false} />
),
filterFn: "includesString",
cell: (i) => <span>{i.getValue() ? "True" : "False"}</span>,
}),
columnHelper.accessor("impersonate", {
header: ({ column }) => (
<SearchableHeader
column={column}
title="Impersonate User"
searchable={false}
/>
),
filterFn: "includesString",
cell: (i) => {
const beSomeone = async () => {
trackLstEvent("impersonateUser_click", {
module: "users",
action: "click",
label: "impersonating user",
page: window.location.pathname,
});
const { data, error } = await authClient.admin.impersonateUser({
userId: i.row.original.id, // required
});
if (data) {
await authClient.getSession();
window.location.replace("/lst/app");
}
if (error) {
console.log(error);
}
};
const cantImpersonate = ["admin", "systemAdmin"];
if (cantImpersonate.includes(i.row.original.role)) return;
return <Button onClick={beSomeone}>Become user</Button>;
},
}),
);
}
return <LstTable data={data} columns={columns} pageSize={50} />;
};
function RouteComponent() {
// const createUser = async () => {
// const { data: newUser, error } = await authClient.admin.createUser({
// email: "cowch@gmail.com", // required
// password: "crazypassword", // required
// name: "James Smith", // required
// role: "manager",
// });
// };
// const besomeone = async () => {
// const { data, error } = await authClient.admin.impersonateUser({
// userId: "iswCNVzQ9cWulbmsaMbeX6e7fV6Eme6t", // required
// });
// await authClient.getSession();
// window.location.replace("/lst/app");
// };
return (
<Suspense fallback={<SkellyTable />}>
<UserTable />
</Suspense>
);
}