From 5d6a339df7628a441d52621466ba873812351109 Mon Sep 17 00:00:00 2001 From: Damian <37555910+DCRepublic@users.noreply.github.com> Date: Tue, 26 Nov 2024 15:53:01 -0500 Subject: [PATCH] DB Migration Stage:1 (new rating system) --- app/actions/getProfs.ts | 34 +++ app/api/getProfClasses/route.ts | 22 +- app/api/submitReview/route.ts | 122 ++++++----- app/rating/page.tsx | 111 ++++++++-- docker-compose.debug.yml | 7 +- .../migration.sql | 41 ++++ .../migration.sql | 2 + .../20241125235114_init/migration.sql | 2 + .../migration.sql | 2 + .../20241126154230_add_prof_uid/migration.sql | 12 ++ .../20241126205047_init/migration.sql | 2 + prisma/schema.prisma | 202 +++++++++--------- swatscraper | 2 +- 13 files changed, 392 insertions(+), 169 deletions(-) create mode 100644 app/actions/getProfs.ts create mode 100644 prisma/migrations/20241125212024_change_faculty/migration.sql create mode 100644 prisma/migrations/20241125212317_change_faculty/migration.sql create mode 100644 prisma/migrations/20241125235114_init/migration.sql create mode 100644 prisma/migrations/20241126152902_change_from_int_to_string/migration.sql create mode 100644 prisma/migrations/20241126154230_add_prof_uid/migration.sql create mode 100644 prisma/migrations/20241126205047_init/migration.sql diff --git a/app/actions/getProfs.ts b/app/actions/getProfs.ts new file mode 100644 index 0000000..4003974 --- /dev/null +++ b/app/actions/getProfs.ts @@ -0,0 +1,34 @@ +"use server"; + +import { cookies } from "next/headers"; +import prisma from "../../lib/prisma"; +import { Prisma } from "@prisma/client"; +import { auth } from "@/lib/auth"; +import { getPlanCookie } from "./actions"; +import { Faculty } from "@prisma/client"; + +export async function getProfs() { + let profs: Faculty[] = []; + let AllProfs = await prisma.faculty.findMany({ + orderBy: { + displayName: "desc", + }, + }); + + console.log(AllProfs.length); + + return AllProfs; +} + +export async function getYears() { + let years: any = []; + let AllCourses = await prisma.course.findMany(); + + for (let i = 0; i < AllCourses.length; i++) { + if (!years.includes(AllCourses[i].year)) { + years.push(AllCourses[i].year); + } + } + + return years; +} diff --git a/app/api/getProfClasses/route.ts b/app/api/getProfClasses/route.ts index 2b79aab..70ce2ce 100644 --- a/app/api/getProfClasses/route.ts +++ b/app/api/getProfClasses/route.ts @@ -1,5 +1,7 @@ +import { clsx } from "clsx"; import { Faculty } from "./../../../node_modules/.prisma/client/index.d"; import { NextResponse, NextRequest } from "next/server"; +import { Course } from "@prisma/client"; import prisma from "../../../lib/prisma"; @@ -7,6 +9,8 @@ export async function GET(request: NextRequest) { const { searchParams } = await new URL(request.url); const profID = parseInt((await searchParams.get("prof")) || "1"); + const outputCourses: Course[] = []; + const courses = await prisma.course.findMany({ include: { instructor: true, @@ -18,5 +22,21 @@ export async function GET(request: NextRequest) { }, }); - return NextResponse.json(courses, { status: 200 }); + for (let i = 0; i < courses.length; i++) { + let push = true; + for (const course of outputCourses) { + if ( + course.courseNumber == courses[i].courseNumber && + course.subject == courses[i].subject && + !course.courseTitle.toLowerCase().includes("lab") + ) { + push = false; + } + } + if (push) { + outputCourses.push(courses[i]); + } + } + + return NextResponse.json(outputCourses, { status: 200 }); } diff --git a/app/api/submitReview/route.ts b/app/api/submitReview/route.ts index 700da0f..d19fb48 100644 --- a/app/api/submitReview/route.ts +++ b/app/api/submitReview/route.ts @@ -15,69 +15,95 @@ export async function POST(request: NextRequest) { uuid: session?.user?.id, }, }); - if (user?.id) { + const theClass = await prisma.course.findUnique({ + where: { + id: parseInt(data.courseID), + }, + }); + const profs = await prisma.faculty.findUnique({ + select: { + displayName: true, + bannerId: true, + uid: true, + }, + + where: { + id: parseInt(data.facultyID), + }, + }); + if (user?.id && theClass && profs) { const newReview = await prisma.rating.create({ data: { userId: user?.id, - courseID: courseID, - facultyID: facultyID, overallRating: data.overallRating, difficulty: data.difficulty, takeAgain: data.takeAgain, forCredit: data.forCredit, grade: data.grade, review: data.review, + courseSubject: theClass.subject, + courseNumber: theClass.courseNumber, + courseName: theClass.courseTitle, + termTaken: data.termTaken, + yearTaken: parseInt(data.yearTaken), + profDisplayName: profs.displayName, + profBannerId: profs.bannerId, + profUid: profs.uid, }, }); - } - const profs = await prisma.faculty.findUnique({ - select: { Rating: { select: { overallRating: true } } }, - //include: { Rating: true }, - where: { id: facultyID }, - }); - - let count: any = { one: 0, two: 0, three: 0, four: 0, five: 0 }; - if (profs) { - if (profs?.Rating?.length > 0) { - for (const rating of profs.Rating) { - if (rating.overallRating == 1) { - count.one += 1; - } - if (rating.overallRating == 2) { - count.two += 1; - } - if (rating.overallRating == 3) { - count.three += 1; - } - if (rating.overallRating == 4) { - count.four += 1; - } - if (rating.overallRating == 5) { - count.five += 1; - } - } - - let avg = parseInt( - ( - (count.one * 1 + - count.two * 2 + - count.three * 3 + - count.four * 4 + - count.five * 5) / - profs.Rating.length - ).toFixed(1) - ); - - const newAvg = await prisma.faculty.update({ - data: { - avgRating: avg, - }, + if (newReview) { + const ratings = await prisma.rating.findMany({ where: { - id: facultyID, + profUid: { + equals: profs.uid, + }, }, }); - return NextResponse.json(newAvg, { status: 200 }); + let count: any = { one: 0, two: 0, three: 0, four: 0, five: 0 }; + + if (ratings) { + if (ratings?.length > 0) { + for (const rating of ratings) { + if (rating.overallRating == 1) { + count.one += 1; + } + if (rating.overallRating == 2) { + count.two += 1; + } + if (rating.overallRating == 3) { + count.three += 1; + } + if (rating.overallRating == 4) { + count.four += 1; + } + if (rating.overallRating == 5) { + count.five += 1; + } + } + + let avg = parseInt( + ( + (count.one * 1 + + count.two * 2 + + count.three * 3 + + count.four * 4 + + count.five * 5) / + ratings.length + ).toFixed(1) + ); + + const newAvg = await prisma.faculty.update({ + data: { + avgRating: avg, + }, + where: { + id: facultyID, + }, + }); + return NextResponse.json(newAvg, { status: 200 }); + } + } } } diff --git a/app/rating/page.tsx b/app/rating/page.tsx index 7dfab87..2d33b1b 100644 --- a/app/rating/page.tsx +++ b/app/rating/page.tsx @@ -18,11 +18,13 @@ import { ModalFooter, useDisclosure, Selection, + Skeleton, Link, } from "@nextui-org/react"; import useSWR from "swr"; -import { useState } from "react"; +import { useEffect, useState } from "react"; import React from "react"; +import { Faculty, Course } from "@prisma/client"; //import { Search, Person, Class, Star } from "@mui/icons-material"; import Person from "@mui/icons-material/Person"; @@ -34,6 +36,7 @@ import Rating from "@mui/material/Rating"; import axios from "axios"; import { factory } from "typescript"; import { Alert } from "@mui/material"; +import { getProfs, getYears } from "../../app/actions/getProfs"; const labels: { [index: string]: string } = { 1: "Awful", @@ -81,6 +84,8 @@ function getLabelText(value: number) { export default function RatingPage() { const [selectedProf, setSelectedProf]: any = useState(1); const [selectedClass, setSelectedClass]: any = useState(); + const [selectedFullClass, setSelectedFullClass] = useState(); + const [selectedProfessor, setSelectedProfessor] = useState(); const [rating, setRating] = React.useState(0); const [hover, setHover] = React.useState(-1); const [diffValue, setDiffValue] = React.useState(0); @@ -92,18 +97,35 @@ export default function RatingPage() { const [submitError, setSubmitError] = React.useState(false); const [grade, setGrade] = React.useState(""); + const [term, setTerm] = React.useState(""); + const [year, setYear] = React.useState(""); + const [yearOptions, setYearOptions] = React.useState>([]); const [review, setReview] = React.useState(""); const { isOpen, onOpen, onOpenChange } = useDisclosure(); - const fetcher = (url: any) => fetch(url).then((r) => r.json()); + const [profs, setProfs] = useState(null); + const fetcher = (url: any) => fetch(url).then((r) => r.json()); + /* const { data: profs, isLoading, error, } = useSWR("/api/getProfs", fetcher, {}); + */ + async function loadProfs() { + const myProfs = await getProfs(); + const myYears = await getYears(); + setProfs(myProfs); + setYearOptions(myYears); + } + useEffect(() => { + // Log the error to an error reporting service + loadProfs(); + }, []); + const { data: classes, isLoading: classesLoading, @@ -121,10 +143,24 @@ export default function RatingPage() { const handleSelectionChange = (e: React.ChangeEvent) => { setGrade(e.target.value); }; + const handleTermChange = (e: React.ChangeEvent) => { + setTerm(e.target.value); + }; + const handleYearChange = (e: React.ChangeEvent) => { + setYear(e.target.value); + }; async function submitReview() { //Reset all vals to defaults... Send to DB! - if (!selectedClass || !selectedProf || rating == 0 || diffValue == 0) { + + if ( + !year || + !term || + !selectedClass || + !selectedProf || + rating == 0 || + diffValue == 0 + ) { alert("Please fill out all required fields!"); } else { await axios @@ -137,6 +173,8 @@ export default function RatingPage() { forCredit: forCredit, grade: grade, review: review, + termTaken: term, + yearTaken: year, }) .then(function (response) { // Handle response @@ -148,8 +186,7 @@ export default function RatingPage() { setSelectedClass([]); setSelectedProf(1); setReview(""); - - setSubmitSuccess(true); + setYear(""), setTerm(""), setSubmitSuccess(true); setTimeout(() => { setSubmitSuccess(false); }, 5000); @@ -202,18 +239,30 @@ export default function RatingPage() { selectedKey={selectedProf} onSelectionChange={onProfSelectionChange} > - {profs?.map((prof: any) => ( - -
-
- {prof.displayName} - - {prof.year} - + {profs ? ( + profs.map((prof: Faculty) => ( + setSelectedProfessor(prof)} + key={prof.id} + textValue={prof.displayName} + > +
+
+ {prof.displayName} + {prof.avgRating ? ( + + Rating: {prof.avgRating} + + ) : null} +
-
+ + )) + ) : ( + + - ))} + )} {classes?.map((thing: any) => ( @@ -240,13 +290,42 @@ export default function RatingPage() { {thing.subject} {thing.courseNumber} - Ref: {thing.courseReferenceNumber.replace(thing.year, "")} + {thing.courseTitle}
))} + +

Select Semester

+
+ + +
Rate Your Professor
diff --git a/docker-compose.debug.yml b/docker-compose.debug.yml index 1a94fa4..3f309a8 100644 --- a/docker-compose.debug.yml +++ b/docker-compose.debug.yml @@ -10,13 +10,13 @@ services: NODE_ENV: development DOMAIN: http://127.0.0.1:3000/ POSTGRES_DB: planner_db + network_mode: "host" env_file: - .env ports: - 3000:3000 - 9229:9229 - networks: - - internal + command: sh -c "npm install --silent && npx prisma migrate dev --name init && npx prisma generate && npm run dev " planner-db-dev: @@ -27,8 +27,7 @@ services: environment: POSTGRES_PASSWORD: example POSTGRES_DB: planner_db - networks: - - internal + network_mode: "host" volumes: - ./postgres:/var/lib/postgresql/data diff --git a/prisma/migrations/20241125212024_change_faculty/migration.sql b/prisma/migrations/20241125212024_change_faculty/migration.sql new file mode 100644 index 0000000..d00d3ae --- /dev/null +++ b/prisma/migrations/20241125212024_change_faculty/migration.sql @@ -0,0 +1,41 @@ +/* + Warnings: + + - You are about to drop the column `courseID` on the `Rating` table. All the data in the column will be lost. + - You are about to drop the column `facultyID` on the `Rating` table. All the data in the column will be lost. + - A unique constraint covering the columns `[uid]` on the table `Faculty` will be added. If there are existing duplicate values, this will fail. + - Added the required column `courseName` to the `Rating` table without a default value. This is not possible if the table is not empty. + - Added the required column `courseNumber` to the `Rating` table without a default value. This is not possible if the table is not empty. + - Added the required column `courseSubject` to the `Rating` table without a default value. This is not possible if the table is not empty. + - Added the required column `profBannerId` to the `Rating` table without a default value. This is not possible if the table is not empty. + - Added the required column `profDisplayName` to the `Rating` table without a default value. This is not possible if the table is not empty. + - Added the required column `termTaken` to the `Rating` table without a default value. This is not possible if the table is not empty. + - Added the required column `yearTaken` to the `Rating` table without a default value. This is not possible if the table is not empty. + +*/ +-- DropForeignKey +ALTER TABLE "Rating" DROP CONSTRAINT "Rating_courseID_fkey"; + +-- DropForeignKey +ALTER TABLE "Rating" DROP CONSTRAINT "Rating_facultyID_fkey"; + +-- AlterTable +ALTER TABLE "Faculty" ADD COLUMN "uid" TEXT NOT NULL DEFAULT 'undefined'; + +-- AlterTable +ALTER TABLE "Rating" DROP COLUMN "courseID", +DROP COLUMN "facultyID", +ADD COLUMN "courseName" TEXT NOT NULL, +ADD COLUMN "courseNumber" TEXT NOT NULL, +ADD COLUMN "courseSubject" TEXT NOT NULL, +ADD COLUMN "facultyId" INTEGER, +ADD COLUMN "profBannerId" TEXT NOT NULL, +ADD COLUMN "profDisplayName" TEXT NOT NULL, +ADD COLUMN "termTaken" INTEGER NOT NULL, +ADD COLUMN "yearTaken" INTEGER NOT NULL; + +-- CreateIndex +CREATE UNIQUE INDEX "Faculty_uid_key" ON "Faculty"("uid"); + +-- AddForeignKey +ALTER TABLE "Rating" ADD CONSTRAINT "Rating_facultyId_fkey" FOREIGN KEY ("facultyId") REFERENCES "Faculty"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/prisma/migrations/20241125212317_change_faculty/migration.sql b/prisma/migrations/20241125212317_change_faculty/migration.sql new file mode 100644 index 0000000..fd6d446 --- /dev/null +++ b/prisma/migrations/20241125212317_change_faculty/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Faculty" ALTER COLUMN "uid" DROP DEFAULT; diff --git a/prisma/migrations/20241125235114_init/migration.sql b/prisma/migrations/20241125235114_init/migration.sql new file mode 100644 index 0000000..70a4e9c --- /dev/null +++ b/prisma/migrations/20241125235114_init/migration.sql @@ -0,0 +1,2 @@ +-- DropIndex +DROP INDEX "Faculty_bannerId_key"; diff --git a/prisma/migrations/20241126152902_change_from_int_to_string/migration.sql b/prisma/migrations/20241126152902_change_from_int_to_string/migration.sql new file mode 100644 index 0000000..fe44cbf --- /dev/null +++ b/prisma/migrations/20241126152902_change_from_int_to_string/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Rating" ALTER COLUMN "termTaken" SET DATA TYPE TEXT; diff --git a/prisma/migrations/20241126154230_add_prof_uid/migration.sql b/prisma/migrations/20241126154230_add_prof_uid/migration.sql new file mode 100644 index 0000000..f3e4a33 --- /dev/null +++ b/prisma/migrations/20241126154230_add_prof_uid/migration.sql @@ -0,0 +1,12 @@ +/* + Warnings: + + - You are about to drop the column `facultyId` on the `Rating` table. All the data in the column will be lost. + +*/ +-- DropForeignKey +ALTER TABLE "Rating" DROP CONSTRAINT "Rating_facultyId_fkey"; + +-- AlterTable +ALTER TABLE "Rating" DROP COLUMN "facultyId", +ADD COLUMN "profUid" TEXT; diff --git a/prisma/migrations/20241126205047_init/migration.sql b/prisma/migrations/20241126205047_init/migration.sql new file mode 100644 index 0000000..24407df --- /dev/null +++ b/prisma/migrations/20241126205047_init/migration.sql @@ -0,0 +1,2 @@ +-- DropIndex +DROP INDEX "Faculty_uid_key"; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 4107bff..32cd5fd 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -1,129 +1,133 @@ generator client { - provider = "prisma-client-js" - previewFeatures = ["fullTextIndex", "fullTextSearch", "relationJoins", "strictUndefinedChecks"] + provider = "prisma-client-js" + previewFeatures = ["fullTextIndex", "fullTextSearch", "relationJoins", "strictUndefinedChecks"] } datasource db { - provider = "postgresql" - url = env("DATABASE_URL") + provider = "postgresql" + url = env("DATABASE_URL") } model User { - id Int @id @default(autoincrement()) - email String @unique - name String? - uuid String @unique - plans CoursePlan[] - ratings Rating[] + id Int @id @default(autoincrement()) + email String @unique + name String? + uuid String @unique + plans CoursePlan[] + ratings Rating[] } model CoursePlan { - id Int @id @default(autoincrement()) - name String - userId Int? - year String - User User? @relation(fields: [userId], references: [id]) - courses Course[] @relation("CourseToCoursePlan") + id Int @id @default(autoincrement()) + name String + userId Int? + year String + User User? @relation(fields: [userId], references: [id]) + courses Course[] @relation("CourseToCoursePlan") } model Course { - id Int @id @default(autoincrement()) - courseId Int - courseReferenceNumber String @unique - courseNumber String - subject String - scheduleTypeDescription String - courseTitle String - descriptionUrl String - description String - creditHours Float - maximumEnrollment Int - enrollment Int - seatsAvailable Int - facultyId Int - facultyMeetId Int - year String - linkedSections String? - avgRating Float? - instructor Faculty @relation(fields: [facultyId], references: [id]) - facultyMeet MeetingsFaculty @relation(fields: [facultyMeetId], references: [id]) - Rating Rating[] - sectionAttributes sectionAttribute[] - CoursePlan CoursePlan[] @relation("CourseToCoursePlan") + id Int @id @default(autoincrement()) + courseId Int + courseReferenceNumber String @unique + courseNumber String + subject String + scheduleTypeDescription String + courseTitle String + descriptionUrl String + description String + creditHours Float + maximumEnrollment Int + enrollment Int + seatsAvailable Int + facultyId Int + facultyMeetId Int + year String + linkedSections String? + avgRating Float? + instructor Faculty @relation(fields: [facultyId], references: [id]) + facultyMeet MeetingsFaculty @relation(fields: [facultyMeetId], references: [id]) + sectionAttributes sectionAttribute[] + CoursePlan CoursePlan[] @relation("CourseToCoursePlan") - @@index([subject, courseNumber], map: "subj_course") + @@index([subject, courseNumber], map: "subj_course") } model Faculty { - id Int @id @default(autoincrement()) - bannerId String @unique - displayName String - emailAddress String - year String - avgRating Float? - courses Course[] - Rating Rating[] + id Int @id @default(autoincrement()) + uid String + bannerId String + displayName String + emailAddress String + year String + avgRating Float? + courses Course[] } model MeetingsFaculty { - id Int @id @default(autoincrement()) - category String - courseReferenceNumber String @unique - meetingTimeID Int - year String - courses Course[] - meetingTimes MeetingTime @relation(fields: [meetingTimeID], references: [id]) + id Int @id @default(autoincrement()) + category String + courseReferenceNumber String @unique + meetingTimeID Int + year String + courses Course[] + meetingTimes MeetingTime @relation(fields: [meetingTimeID], references: [id]) } model MeetingTime { - id Int @id @default(autoincrement()) - building String - buildingDescription String - room String - category String - courseReferenceNumber String @unique - endDate String - startDate String - hoursWeek Float - meetingType String - meetingTypeDescription String - monday Boolean - tuesday Boolean - wednesday Boolean - thursday Boolean - friday Boolean - saturday Boolean - sunday Boolean - year String - beginTime String - endTime String - facultyMeet MeetingsFaculty[] + id Int @id @default(autoincrement()) + building String + buildingDescription String + room String + category String + courseReferenceNumber String @unique + endDate String + startDate String + hoursWeek Float + meetingType String + meetingTypeDescription String + monday Boolean + tuesday Boolean + wednesday Boolean + thursday Boolean + friday Boolean + saturday Boolean + sunday Boolean + year String + beginTime String + endTime String + facultyMeet MeetingsFaculty[] } model sectionAttribute { - id Int @id @default(autoincrement()) - code String - description String - courseReferenceNumber String @unique - year String - courseId Int - Course Course @relation(fields: [courseId], references: [id]) + id Int @id @default(autoincrement()) + code String + description String + courseReferenceNumber String @unique + year String + courseId Int + Course Course @relation(fields: [courseId], references: [id]) } model Rating { - id Int @id @default(autoincrement()) - courseID Int - facultyID Int - overallRating Int? - difficulty Int? - takeAgain Boolean? - review String? - grade String? - forCredit Boolean? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - userId Int - course Course @relation(fields: [courseID], references: [id]) - faculty Faculty @relation(fields: [facultyID], references: [id]) - User User? @relation(fields: [userId], references: [id]) + id Int @id @default(autoincrement()) + courseSubject String + courseNumber String + courseName String + profDisplayName String + profBannerId String + yearTaken Int + termTaken String + profUid String? + + overallRating Int? + difficulty Int? + takeAgain Boolean? + review String? + grade String? + forCredit Boolean? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + userId Int + User User? @relation(fields: [userId], references: [id]) } diff --git a/swatscraper b/swatscraper index ed190e6..9a516e5 160000 --- a/swatscraper +++ b/swatscraper @@ -1 +1 @@ -Subproject commit ed190e652c3e2380e69976869d8e3a2a89a04ab4 +Subproject commit 9a516e53feae78416d2147eb934604e890677a18