Role overhaul
This commit is contained in:
parent
c402732f0a
commit
95c9314d03
8 changed files with 114 additions and 96 deletions
|
@ -1,9 +1,3 @@
|
|||
enum RoleValue {
|
||||
USER
|
||||
ADMIN
|
||||
VIP
|
||||
}
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
@ -47,7 +41,6 @@ model Session {
|
|||
|
||||
model User {
|
||||
id String @id @default(cuid())
|
||||
role RoleValue @default(USER)
|
||||
createdAt DateTime @default(now())
|
||||
name String?
|
||||
email String? @unique
|
||||
|
@ -58,6 +51,9 @@ model User {
|
|||
rooms Room[]
|
||||
votes Vote[]
|
||||
logs Log[]
|
||||
isAdmin Boolean @default(false)
|
||||
isVIP Boolean @default(false)
|
||||
|
||||
}
|
||||
|
||||
model VerificationToken {
|
||||
|
|
|
@ -7,7 +7,6 @@ import { IoTrashBinOutline } from "react-icons/io5";
|
|||
import { SiGithub, SiGoogle } from "react-icons/si";
|
||||
import { GiStarFormation } from "react-icons/gi";
|
||||
import { api } from "~/utils/api";
|
||||
import type { Role } from "~/utils/types";
|
||||
import { getServerAuthSession } from "../../server/auth";
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = async (ctx) => {
|
||||
|
@ -23,7 +22,7 @@ export const getServerSideProps: GetServerSideProps = async (ctx) => {
|
|||
};
|
||||
}
|
||||
|
||||
if (session.user.role !== "ADMIN") {
|
||||
if (!session.user.isAdmin) {
|
||||
ctx.res.statusCode = 403;
|
||||
return {
|
||||
redirect: {
|
||||
|
@ -91,7 +90,8 @@ const AdminBody: React.FC = () => {
|
|||
id: string;
|
||||
}[];
|
||||
id: string;
|
||||
role: Role;
|
||||
isAdmin: boolean;
|
||||
isVIP: boolean;
|
||||
name: string | null;
|
||||
email: string | null;
|
||||
}) => {
|
||||
|
@ -120,7 +120,13 @@ const AdminBody: React.FC = () => {
|
|||
},
|
||||
});
|
||||
|
||||
const setRoleMutation = api.user.setRole.useMutation({
|
||||
const setAdminMutation = api.user.setAdmin.useMutation({
|
||||
onSuccess: async () => {
|
||||
await refetchData();
|
||||
},
|
||||
});
|
||||
|
||||
const setVIPMutation = api.user.setVIP.useMutation({
|
||||
onSuccess: async () => {
|
||||
await refetchData();
|
||||
},
|
||||
|
@ -138,8 +144,12 @@ const AdminBody: React.FC = () => {
|
|||
await clearSessionsMutation.mutateAsync();
|
||||
};
|
||||
|
||||
const setUserRoleHandler = async (userId: string, role: Role) => {
|
||||
await setRoleMutation.mutateAsync({ userId, role });
|
||||
const setAdmin = async (userId: string, value: boolean) => {
|
||||
await setAdminMutation.mutateAsync({ userId, value });
|
||||
};
|
||||
|
||||
const setVIP = async (userId: string, value: boolean) => {
|
||||
await setVIPMutation.mutateAsync({ userId, value });
|
||||
};
|
||||
|
||||
const refetchData = async () => {
|
||||
|
@ -264,36 +274,28 @@ const AdminBody: React.FC = () => {
|
|||
</td>
|
||||
<td>
|
||||
<button className="m-2">
|
||||
{user.role === "ADMIN" ? (
|
||||
{user.isAdmin ? (
|
||||
<FaShieldAlt
|
||||
className="text-xl inline-block text-primary"
|
||||
onClick={() =>
|
||||
void setUserRoleHandler(user.id, "USER")
|
||||
}
|
||||
onClick={() => void setAdmin(user.id, false)}
|
||||
/>
|
||||
) : (
|
||||
<FaShieldAlt
|
||||
className="text-xl inline-block"
|
||||
onClick={() =>
|
||||
void setUserRoleHandler(user.id, "ADMIN")
|
||||
}
|
||||
onClick={() => void setAdmin(user.id, true)}
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
<button className="m-2">
|
||||
{user.role === "VIP" ? (
|
||||
{user.isVIP ? (
|
||||
<GiStarFormation
|
||||
className="text-xl inline-block text-secondary"
|
||||
onClick={() =>
|
||||
void setUserRoleHandler(user.id, "USER")
|
||||
}
|
||||
onClick={() => void setVIP(user.id, false)}
|
||||
/>
|
||||
) : (
|
||||
<GiStarFormation
|
||||
className="text-xl inline-block"
|
||||
onClick={() =>
|
||||
void setUserRoleHandler(user.id, "VIP")
|
||||
}
|
||||
onClick={() => void setVIP(user.id, true)}
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
|
|
|
@ -59,10 +59,10 @@ const HomePageBody: React.FC = () => {
|
|||
<>
|
||||
<h1 className="flex flex-row flex-wrap text-center justify-center items-center gap-1 text-4xl font-bold mx-auto">
|
||||
Hi, {sessionData?.user.name}!{" "}
|
||||
{sessionData?.user.role === "ADMIN" && (
|
||||
{sessionData?.user.isAdmin && (
|
||||
<FaShieldAlt className="inline-block text-primary" />
|
||||
)}
|
||||
{sessionData?.user.role === "VIP" && (
|
||||
{sessionData?.user.isVIP && (
|
||||
<GiStarFormation className="inline-block text-secondary" />
|
||||
)}
|
||||
</h1>
|
||||
|
|
|
@ -9,6 +9,7 @@ import { FaShieldAlt } from "react-icons/fa";
|
|||
import { SiGithub, SiGoogle } from "react-icons/si";
|
||||
import { api } from "~/utils/api";
|
||||
import { getServerAuthSession } from "../../server/auth";
|
||||
import { GiStarFormation } from "react-icons/gi";
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = async (ctx) => {
|
||||
const session = await getServerAuthSession(ctx);
|
||||
|
@ -103,7 +104,7 @@ const ProfileBody: React.FC = () => {
|
|||
<label
|
||||
htmlFor="delete-user-modal"
|
||||
className="btn btn-error"
|
||||
onClick={ () => void deleteCurrentUser() }
|
||||
onClick={() => void deleteCurrentUser()}
|
||||
>
|
||||
I am sure!
|
||||
</label>
|
||||
|
@ -114,79 +115,82 @@ const ProfileBody: React.FC = () => {
|
|||
<div className="card w-90 bg-neutral shadow-xl">
|
||||
<div className="card-body">
|
||||
<h2 className="card-title">Profile:</h2>
|
||||
{ sessionData.user.image && (
|
||||
<div className="indicator mx-auto m-4">
|
||||
{ sessionData.user.role === "ADMIN" && (
|
||||
<span className="indicator-item indicator-bottom badge badge-primary">
|
||||
<div className="tooltip tooltip-primary" data-tip="Admin">
|
||||
<FaShieldAlt className="text-xl" />
|
||||
</div>
|
||||
</span>
|
||||
) }
|
||||
{sessionData.user.image && (
|
||||
<Image
|
||||
className="mx-auto"
|
||||
src={sessionData.user.image}
|
||||
alt="Profile picture."
|
||||
height={100}
|
||||
width={100}
|
||||
priority
|
||||
/>
|
||||
)}
|
||||
|
||||
<Image
|
||||
className="mx-auto"
|
||||
src={ sessionData.user.image }
|
||||
alt="Profile picture."
|
||||
height={ 100 }
|
||||
width={ 100 }
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
) }
|
||||
<div className="flex flex-row flex-wrap items-center text-center justify-center gap-4">
|
||||
{sessionData.user.isAdmin && (
|
||||
<div className="tooltip tooltip-primary" data-tip="Admin">
|
||||
<FaShieldAlt className="text-xl text-primary" />
|
||||
</div>
|
||||
)}
|
||||
{sessionData.user.isVIP && (
|
||||
<div className="tooltip tooltip-secondary" data-tip="VIP">
|
||||
<GiStarFormation className="inline-block text-xl text-secondary" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{ providersLoading ? (
|
||||
{providersLoading ? (
|
||||
<div className="mx-auto">
|
||||
<span className="loading loading-dots loading-lg"></span>{ " " }
|
||||
<span className="loading loading-dots loading-lg"></span>{" "}
|
||||
</div>
|
||||
) : (
|
||||
<div className="mx-auto">
|
||||
<button
|
||||
className={ `btn btn-square btn-outline mx-2` }
|
||||
disabled={ providers?.includes("github") }
|
||||
onClick={ () => void signIn("github") }
|
||||
className={`btn btn-square btn-outline mx-2`}
|
||||
disabled={providers?.includes("github")}
|
||||
onClick={() => void signIn("github")}
|
||||
>
|
||||
<SiGithub />
|
||||
</button>
|
||||
|
||||
<button
|
||||
className={ `btn btn-square btn-outline mx-2` }
|
||||
disabled={ providers?.includes("google") }
|
||||
onClick={ () => void signIn("google") }
|
||||
className={`btn btn-square btn-outline mx-2`}
|
||||
disabled={providers?.includes("google")}
|
||||
onClick={() => void signIn("google")}
|
||||
>
|
||||
<SiGoogle />
|
||||
</button>
|
||||
</div>
|
||||
) }
|
||||
)}
|
||||
|
||||
{ sessionData.user.name && (
|
||||
{sessionData.user.name && (
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Name"
|
||||
className="input input-bordered"
|
||||
value={ nameText }
|
||||
onChange={ (event) => setNameText(event.target.value) }
|
||||
value={nameText}
|
||||
onChange={(event) => setNameText(event.target.value)}
|
||||
/>
|
||||
) }
|
||||
)}
|
||||
|
||||
{ sessionData.user.email && (
|
||||
{sessionData.user.email && (
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Email"
|
||||
className="input input-bordered"
|
||||
value={ sessionData.user.email }
|
||||
value={sessionData.user.email}
|
||||
disabled
|
||||
/>
|
||||
) }
|
||||
)}
|
||||
|
||||
<button
|
||||
onClick={ () => void saveUser() }
|
||||
onClick={() => void saveUser()}
|
||||
className="btn btn-secondary"
|
||||
>
|
||||
Save Account
|
||||
</button>
|
||||
|
||||
{/* <button className="btn btn-error">Delete Account</button> */ }
|
||||
{/* <button className="btn btn-error">Delete Account</button> */}
|
||||
|
||||
<label htmlFor="delete-user-modal" className="btn btn-error">
|
||||
Delete Account
|
||||
|
|
|
@ -110,7 +110,8 @@ const RoomBody: React.FC = ({}) => {
|
|||
name: sessionData?.user.name || "",
|
||||
image: sessionData?.user.image || "",
|
||||
client_id: sessionData?.user.id || "",
|
||||
role: sessionData?.user.role || "USER",
|
||||
isAdmin: sessionData?.user.isAdmin || false,
|
||||
isVIP: sessionData?.user.isVIP || false,
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -303,7 +304,7 @@ const RoomBody: React.FC = ({}) => {
|
|||
|
||||
<p className="flex flex-row flex-wrap text-center justify-center items-center gap-1 text-md mx-auto">
|
||||
{presenceItem.data.name}{" "}
|
||||
{presenceItem.data.role === "ADMIN" && (
|
||||
{presenceItem.data.isAdmin && (
|
||||
<div
|
||||
className="tooltip tooltip-primary"
|
||||
data-tip="Admin"
|
||||
|
@ -311,7 +312,7 @@ const RoomBody: React.FC = ({}) => {
|
|||
<FaShieldAlt className="inline-block text-primary" />
|
||||
</div>
|
||||
)}{" "}
|
||||
{presenceItem.data.role === "VIP" && (
|
||||
{presenceItem.data.isVIP && (
|
||||
<div
|
||||
className="tooltip tooltip-secondary"
|
||||
data-tip="VIP"
|
||||
|
|
|
@ -4,7 +4,6 @@ import { z } from "zod";
|
|||
import { Goodbye } from "~/components/templates/Goodbye";
|
||||
import { env } from "~/env.mjs";
|
||||
import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc";
|
||||
import type { Role } from "~/utils/types";
|
||||
|
||||
import { fetchCache, invalidateCache, setCache } from "~/server/redis";
|
||||
|
||||
|
@ -54,7 +53,8 @@ export const userRouter = createTRPCRouter({
|
|||
}[];
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
role: Role;
|
||||
isAdmin: boolean;
|
||||
isVIP: boolean;
|
||||
name: string | null;
|
||||
email: string | null;
|
||||
}[]
|
||||
|
@ -72,7 +72,8 @@ export const userRouter = createTRPCRouter({
|
|||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
role: true,
|
||||
isAdmin: true,
|
||||
isVIP: true,
|
||||
createdAt: true,
|
||||
email: true,
|
||||
sessions: {
|
||||
|
@ -103,7 +104,7 @@ export const userRouter = createTRPCRouter({
|
|||
)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
let user: User;
|
||||
if (input?.userId && ctx.session.user.role === "ADMIN") {
|
||||
if (input?.userId && ctx.session.user.isAdmin) {
|
||||
user = await ctx.prisma.user.delete({
|
||||
where: {
|
||||
id: input.userId,
|
||||
|
@ -149,15 +150,11 @@ export const userRouter = createTRPCRouter({
|
|||
|
||||
return !!user;
|
||||
}),
|
||||
setRole: protectedProcedure
|
||||
setAdmin: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
userId: z.string(),
|
||||
role: z.union([
|
||||
z.literal("ADMIN"),
|
||||
z.literal("USER"),
|
||||
z.literal("VIP"),
|
||||
]),
|
||||
value: z.boolean(),
|
||||
})
|
||||
)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
|
@ -166,7 +163,29 @@ export const userRouter = createTRPCRouter({
|
|||
id: input.userId,
|
||||
},
|
||||
data: {
|
||||
role: input.role,
|
||||
isAdmin: input.value,
|
||||
},
|
||||
});
|
||||
|
||||
await invalidateCache(`kv_userlist_admin`);
|
||||
|
||||
return !!user;
|
||||
}),
|
||||
|
||||
setVIP: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
userId: z.string(),
|
||||
value: z.boolean(),
|
||||
})
|
||||
)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const user = await ctx.prisma.user.update({
|
||||
where: {
|
||||
id: input.userId,
|
||||
},
|
||||
data: {
|
||||
isVIP: input.value,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
import { PrismaAdapter } from "@auth/prisma-adapter";
|
||||
import { type GetServerSidePropsContext } from "next";
|
||||
import {
|
||||
getServerSession,
|
||||
type DefaultSession,
|
||||
type NextAuthOptions,
|
||||
getServerSession,
|
||||
type DefaultSession,
|
||||
type NextAuthOptions,
|
||||
} from "next-auth";
|
||||
import GithubProvider from "next-auth/providers/github";
|
||||
import GoogleProvider from "next-auth/providers/google";
|
||||
import { Resend } from "resend";
|
||||
import { env } from "~/env.mjs";
|
||||
import { prisma } from "~/server/db";
|
||||
import type { Role } from "~/utils/types";
|
||||
import { Welcome } from "../components/templates/Welcome";
|
||||
import { invalidateCache } from "./redis";
|
||||
|
||||
|
@ -26,12 +25,14 @@ declare module "next-auth" {
|
|||
interface Session extends DefaultSession {
|
||||
user: {
|
||||
id: string;
|
||||
role: Role;
|
||||
isAdmin: boolean;
|
||||
isVIP: boolean;
|
||||
} & DefaultSession["user"];
|
||||
}
|
||||
|
||||
interface User {
|
||||
role: Role;
|
||||
isAdmin: boolean;
|
||||
isVIP: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -45,7 +46,8 @@ export const authOptions: NextAuthOptions = {
|
|||
session({ session, user }) {
|
||||
if (session.user) {
|
||||
session.user.id = user.id;
|
||||
session.user.role = user.role;
|
||||
session.user.isAdmin = user.isAdmin;
|
||||
session.user.isVIP = user.isVIP;
|
||||
}
|
||||
return session;
|
||||
},
|
||||
|
|
|
@ -7,16 +7,10 @@ const EventTypes = {
|
|||
} as const;
|
||||
export type EventType = BetterEnum<typeof EventTypes>;
|
||||
|
||||
const RoleValues = {
|
||||
ADMIN: "ADMIN",
|
||||
USER: "USER",
|
||||
VIP: "VIP",
|
||||
} as const;
|
||||
export type Role = BetterEnum<typeof RoleValues>;
|
||||
|
||||
export interface PresenceItem {
|
||||
name: string;
|
||||
image: string;
|
||||
client_id: string;
|
||||
role: Role;
|
||||
isAdmin: boolean;
|
||||
isVIP: boolean;
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue