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