From 7519810753651901842ee694e92f13c45e5a25c7 Mon Sep 17 00:00:00 2001 From: Damian <37555910+DCRepublic@users.noreply.github.com> Date: Mon, 11 Nov 2024 12:19:32 -0500 Subject: [PATCH] Add admin dashboard, and ability to delete ratings --- app/admin/layout.tsx | 13 ++++ app/admin/page.tsx | 128 ++++++++++++++++++++++++++++++++++ app/api/deleteRating/route.ts | 20 ++++++ app/api/getRatings/route.ts | 18 +++++ components/navbar.tsx | 17 +++-- lib/auth.ts | 37 +++++++++- 6 files changed, 221 insertions(+), 12 deletions(-) create mode 100644 app/admin/layout.tsx create mode 100644 app/admin/page.tsx create mode 100644 app/api/deleteRating/route.ts create mode 100644 app/api/getRatings/route.ts diff --git a/app/admin/layout.tsx b/app/admin/layout.tsx new file mode 100644 index 0000000..4bb84a9 --- /dev/null +++ b/app/admin/layout.tsx @@ -0,0 +1,13 @@ +export default function DocsLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( +
+
+ {children} +
+
+ ); +} diff --git a/app/admin/page.tsx b/app/admin/page.tsx new file mode 100644 index 0000000..b1e7439 --- /dev/null +++ b/app/admin/page.tsx @@ -0,0 +1,128 @@ +"use client"; + +import useSWR from "swr"; +import { useState } from "react"; +import React from "react"; +import { signIn, signOut, useSession } from "next-auth/react"; +import { + Table, + TableHeader, + TableColumn, + TableBody, + TableRow, + TableCell, + getKeyValue, + Dropdown, + DropdownTrigger, + Button, + DropdownMenu, + DropdownItem, +} from "@nextui-org/react"; + +import { MoreVert } from "@mui/icons-material"; +import axios from "axios"; + +export default function AdminPage() { + const { data: session, status } = useSession(); + const fetcher = (url: any) => fetch(url).then((r) => r.json()); + + const { + data: ratings, + isLoading, + error, + } = useSWR("/api/getRatings", fetcher, { refreshInterval: 5000 }); + let columns = []; + let filtered_ratings: any = []; + + if (!isLoading) { + for (let rating of ratings) { + const name = rating.User?.name; + const email = rating.User?.email; + rating.name = name; + rating.email = email; + + filtered_ratings.push(rating); + } + + if (filtered_ratings.length > 0) { + for (let key in filtered_ratings[0]) { + if (key != "User") { + columns.push({ key: key, label: key }); + } + } + } + columns.push({ key: "actions", value: "actions" }); + } + + async function deleteRating(ratingID: any) { + console.log("Rating Gone, finito, finished"); + await axios + .post("/api/deleteRating", { + ratingID: ratingID, + }) + .then(function (response) { + // Handle response + console.log(response); + }) + .catch(function (error) { + console.log(error); + }); + } + + const renderCell = React.useCallback((user: any, columnKey: React.Key) => { + const cellValue = user[columnKey as keyof any]; + + switch (columnKey) { + case "actions": + return ( +
+ + + + + + deleteRating(user.id)}> + Delete + + + +
+ ); + default: + return cellValue; + } + }, []); + + if (status === "authenticated") { + // @ts-ignore + if (session.user?.role === "admin") { + return ( +
+ + + {(column) => ( + {column.label} + )} + + + {(item: any) => ( + + {(columnKey) => ( + {renderCell(item, columnKey)} + )} + + )} + +
+
+ ); + } + } +} diff --git a/app/api/deleteRating/route.ts b/app/api/deleteRating/route.ts new file mode 100644 index 0000000..98f493a --- /dev/null +++ b/app/api/deleteRating/route.ts @@ -0,0 +1,20 @@ +// api/test.ts +import { NextResponse, NextRequest } from "next/server"; + +import prisma from "../../../lib/prisma"; +import { auth } from "../../../lib/auth"; +import { getPlanCookie } from "../../../app/actions"; + +export async function POST(request: NextRequest) { + const data = await request.json(); + + const ratingID = data.ratingID; + + const courses = await prisma.rating.delete({ + where: { + id: parseInt(ratingID), + }, + }); + + return NextResponse.json(courses, { status: 200 }); +} diff --git a/app/api/getRatings/route.ts b/app/api/getRatings/route.ts new file mode 100644 index 0000000..4ded146 --- /dev/null +++ b/app/api/getRatings/route.ts @@ -0,0 +1,18 @@ +import { NextResponse, NextRequest } from "next/server"; + +import prisma from "../../../lib/prisma"; + +export async function GET(request: NextRequest) { + const profs = await prisma.rating.findMany({ + include: { + User: { + select: { + name: true, + email: true, + }, + }, + }, + }); + + return NextResponse.json(profs, { status: 200 }); +} diff --git a/components/navbar.tsx b/components/navbar.tsx index 946387a..14ff9ed 100644 --- a/components/navbar.tsx +++ b/components/navbar.tsx @@ -41,8 +41,8 @@ export const Navbar = (props: any) => { const { data: session, status } = useSession(); let authenticated; - let loginLink; - let adminDashLink; + let loginLink: any; + let adminDashLink: any; let nameButton; if (props.hasOwnProperty("login")) { @@ -68,16 +68,14 @@ export const Navbar = (props: any) => { ); // @ts-ignore - /* + if (session.user?.role === "admin") { adminDashLink = ( - - -
Admin
- -
+ +
Admin
+
); - }*/ + } nameButton = session.user?.name; } else { authenticated = false; @@ -184,6 +182,7 @@ export const Navbar = (props: any) => { {authenticated ? ( + {adminDashLink} {loginLink} ) : ( diff --git a/lib/auth.ts b/lib/auth.ts index 63a140a..7dc545d 100644 --- a/lib/auth.ts +++ b/lib/auth.ts @@ -4,10 +4,21 @@ import type { NextApiResponse, } from "next"; import type { NextAuthOptions } from "next-auth"; +import { getToken } from "next-auth/jwt"; -import KeycloakProvider from "next-auth/providers/keycloak"; +import KeycloakProvider, { + KeycloakProfileToken, +} from "next-auth/providers/keycloak"; import { getServerSession } from "next-auth"; +declare module "next-auth/providers/keycloak" { + export interface KeycloakProfileToken extends KeycloakProfile { + realm_access: { roles: [string] }; + } +} +function parseJwt(token: string) { + return JSON.parse(Buffer.from(token.split(".")[1], "base64").toString()); +} // You'll need to import and pass this // to `NextAuth` in `app/api/auth/[...nextauth]/route.ts` export const config = { @@ -22,15 +33,36 @@ export const config = { providers: [ KeycloakProvider({ profile(profile, tokens) { + const tokenData: KeycloakProfileToken = parseJwt( + //@ts-ignore + tokens.access_token + ); + + let theRole; + + if ("scheduler" in tokenData.resource_access) { + theRole = tokenData.resource_access.scheduler.roles.find( + (role: string) => role === "admin" || "user" + ); + } else { + theRole = "user"; + } + return { id: profile.sub, name: profile.name, email: profile.email, + ///THE DUMB OLD WAY BELOW //MAKE SURE TO ADD GROUPS UNDER client scopes ->[currentproject]-dedicated //If you don't it wont redirect + + //New way is to add a role under client scopes-> roles + // Then assign that role to your user specifically role: - profile.groups.find((group: string) => group === "admin") || "user", + //tokenData.resource_access.scheduler.roles.find( (role: string) => role === "admin" || "user"), + //profile.groups.find((group: string) => group === "admin") || "user", + theRole, }; }, clientId: process.env.KEYCLOAK_ID || "", @@ -53,7 +85,6 @@ export const config = { // @ts-ignore session.user.id = token.sub; } - return session; }, },