From 97c74bb414715f7b71e7c3f6cf47afa316ed916a Mon Sep 17 00:00:00 2001 From: Damian <37555910+DCRepublic@users.noreply.github.com> Date: Sun, 10 Nov 2024 14:06:24 -0500 Subject: [PATCH] DB: shcema for ratings, fix build error --- app/api/getCourses/route.ts | 15 ++ app/rating/layout.tsx | 13 ++ app/rating/page.tsx | 56 ++++++++ components/CourseCard.tsx | 136 +++++++++++------- package-lock.json | 65 +++++++++ package.json | 1 + .../20241109035411_add_ratings/migration.sql | 29 ++++ .../20241109040421_add_ratings1/migration.sql | 5 + prisma/schema.prisma | 63 +++++--- swatscraper | 2 +- 10 files changed, 313 insertions(+), 72 deletions(-) create mode 100644 app/api/getCourses/route.ts create mode 100644 app/rating/layout.tsx create mode 100644 app/rating/page.tsx create mode 100644 prisma/migrations/20241109035411_add_ratings/migration.sql create mode 100644 prisma/migrations/20241109040421_add_ratings1/migration.sql diff --git a/app/api/getCourses/route.ts b/app/api/getCourses/route.ts new file mode 100644 index 0000000..b4c5cfb --- /dev/null +++ b/app/api/getCourses/route.ts @@ -0,0 +1,15 @@ +import { NextResponse, NextRequest } from "next/server"; +import prisma from "../../../lib/prisma"; +import { auth } from "../../../lib/auth"; + +export async function GET(request: NextRequest) { + const courses = await prisma.course.findMany({ + select: { + id: true, + courseTitle: true, + }, + where: { year: "S2025" }, + }); + //console.log(plans); + return NextResponse.json(courses, { status: 200 }); +} diff --git a/app/rating/layout.tsx b/app/rating/layout.tsx new file mode 100644 index 0000000..2332d1e --- /dev/null +++ b/app/rating/layout.tsx @@ -0,0 +1,13 @@ +export default function DocsLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( +
+
+ {children} +
+
+ ); +} diff --git a/app/rating/page.tsx b/app/rating/page.tsx new file mode 100644 index 0000000..3a01ee5 --- /dev/null +++ b/app/rating/page.tsx @@ -0,0 +1,56 @@ +"use client"; +import { + Card, + CardBody, + CardHeader, + Textarea, + Select, + SelectItem, + Popover, + Autocomplete, + AutocompleteItem, + Button, + Chip, + Input, +} from "@nextui-org/react"; +import useSWR from "swr"; + +export default function RatingPage() { + const fetcher = (url: any) => fetch(url).then((r) => r.json()); + const { + data: courses, + isLoading, + error, + } = useSWR("/api/getCourses", fetcher, {}); + return ( + + +

+ Leave a Rating +

+
+ + + {courses?.map((course: any) => ( + + {course.courseTitle} + + ))} + + + + +
+ ); +} diff --git a/components/CourseCard.tsx b/components/CourseCard.tsx index 5d3a076..408ee9b 100644 --- a/components/CourseCard.tsx +++ b/components/CourseCard.tsx @@ -10,6 +10,7 @@ import { PopoverTrigger, PopoverContent, Button, + Chip, } from "@nextui-org/react"; import Image from "next/image"; import { tv } from "tailwind-variants"; @@ -18,7 +19,7 @@ import { ScrollShadow } from "@nextui-org/react"; import { InstructorCard } from "./InstructorCard"; import AddIcon from "@mui/icons-material/Add"; -import ErrorRoundedIcon from '@mui/icons-material/ErrorRounded'; +import ErrorRoundedIcon from "@mui/icons-material/ErrorRounded"; import axios from "axios"; @@ -46,7 +47,7 @@ async function updatePlan(course: any) { } export default function CourseCard(props: any) { - console.log(props.course) + console.log(props.course); let color = generateColorFromName(props.course.subject); const color_mappings: { [key: string]: string } = { @@ -58,11 +59,11 @@ export default function CourseCard(props: any) { }; const days = { - "M": props.course.facultyMeet.meetingTimes.monday, - "T": props.course.facultyMeet.meetingTimes.tuesday, - "W": props.course.facultyMeet.meetingTimes.wednesday, - "TH": props.course.facultyMeet.meetingTimes.thursday, - "F": props.course.facultyMeet.meetingTimes.friday + M: props.course.facultyMeet.meetingTimes.monday, + T: props.course.facultyMeet.meetingTimes.tuesday, + W: props.course.facultyMeet.meetingTimes.wednesday, + TH: props.course.facultyMeet.meetingTimes.thursday, + F: props.course.facultyMeet.meetingTimes.friday, }; const coloredDays = Object.entries(days).map((item, index) => { @@ -80,13 +81,19 @@ export default function CourseCard(props: any) { return null; }); - const attributeCodes = props.course.sectionAttributes.map((item: any, index: number) => { - if (item) { - return

{item.code}

- } + const attributeCodes = props.course.sectionAttributes.map( + (item: any, index: number) => { + if (item) { + return ( + +

{item.code}

+
+ ); + } - return null; - }); + return null; + } + ); return ( @@ -95,22 +102,26 @@ export default function CourseCard(props: any) { style={{ backgroundColor: color }} />
- + updatePlan(props.course)}>

- {props.course.courseTitle} + {props.course.courseTitle.replace("&", "&")}

-

+

{props.course.subject} {props.course.courseNumber} |{" "} - {props.course.creditHours} credit(s) | - {props.course.sectionAttributes.length > 0 && attributeCodes} + {props.course.creditHours} credit(s) |   + {props.course.sectionAttributes.length > 0 && ( +
{attributeCodes}
+ )}

Professor
- {props.course.facultyMeet.meetingTimes.room ? (
-

- {props.course.facultyMeet.meetingTimes.buildingDescription}{" "} - {props.course.facultyMeet.meetingTimes.room} -

-
- {props.course.facultyMeet.meetingTimes ? ( -
-

- {" "} - {props.course.facultyMeet.meetingTimes.beginTime.slice(0, 2) + - ":" + - props.course.facultyMeet.meetingTimes.beginTime.slice(2)}{" "} - -{" "} - {props.course.facultyMeet.meetingTimes.endTime.slice(0, 2) + - ":" + - props.course.facultyMeet.meetingTimes.endTime.slice(2)} -

-
- ) : null} + {props.course.facultyMeet.meetingTimes.room ? ( +
+
+ {props.course.facultyMeet.meetingTimes.buildingDescription}{" "} + {props.course.facultyMeet.meetingTimes.room} +
+
+ {props.course.facultyMeet.meetingTimes ? ( +
+
+ {" "} + {props.course.facultyMeet.meetingTimes.beginTime.slice( + 0, + 2 + ) + + ":" + + props.course.facultyMeet.meetingTimes.beginTime.slice( + 2 + )}{" "} + -{" "} + {props.course.facultyMeet.meetingTimes.endTime.slice( + 0, + 2 + ) + + ":" + + props.course.facultyMeet.meetingTimes.endTime.slice( + 2 + )} +
+
+ ) : null} +
-
) :
-

Contact your Professor for additional details.

-
} -
-
- {coloredDays} + ) : ( +
+

+ Contact your Professor for additional details. +

+ )} +
+
{coloredDays}
-

Instructor

-

{props.course.instructor.displayName}

+
Instructor
+
+ {props.course.instructor.displayName.replace("'", "'")} +
-

?

+
?
- {props.course.seatsAvailable >= 0 &&
-

No available seats left for this section

- -
} + {props.course.seatsAvailable >= 0 && ( +
+
+ No available seats left for this section +
+ {/* + */} +
+ )}
diff --git a/package-lock.json b/package-lock.json index dab83bd..a175baa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -55,6 +55,7 @@ "next-client-cookies": "^2.0.0", "next-themes": "^0.3.0", "postcss": "8.4.47", + "prisma": "^5.22.0", "react": "18.3.1", "react-dom": "18.3.1", "swr": "^2.2.5", @@ -3167,6 +3168,51 @@ } } }, + "node_modules/@prisma/debug": { + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.22.0.tgz", + "integrity": "sha512-AUt44v3YJeggO2ZU5BkXI7M4hu9BF2zzH2iF2V5pyXT/lRTyWiElZ7It+bRH1EshoMRxHgpYg4VB6rCM+mG5jQ==", + "license": "Apache-2.0" + }, + "node_modules/@prisma/engines": { + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.22.0.tgz", + "integrity": "sha512-UNjfslWhAt06kVL3CjkuYpHAWSO6L4kDCVPegV6itt7nD1kSJavd3vhgAEhjglLJJKEdJ7oIqDJ+yHk6qO8gPA==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "5.22.0", + "@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2", + "@prisma/fetch-engine": "5.22.0", + "@prisma/get-platform": "5.22.0" + } + }, + "node_modules/@prisma/engines-version": { + "version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2.tgz", + "integrity": "sha512-2PTmxFR2yHW/eB3uqWtcgRcgAbG1rwG9ZriSvQw+nnb7c4uCr3RAcGMb6/zfE88SKlC1Nj2ziUvc96Z379mHgQ==", + "license": "Apache-2.0" + }, + "node_modules/@prisma/fetch-engine": { + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.22.0.tgz", + "integrity": "sha512-bkrD/Mc2fSvkQBV5EpoFcZ87AvOgDxbG99488a5cexp5Ccny+UM6MAe/UFkUC0wLYD9+9befNOqGiIJhhq+HbA==", + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "5.22.0", + "@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2", + "@prisma/get-platform": "5.22.0" + } + }, + "node_modules/@prisma/get-platform": { + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.22.0.tgz", + "integrity": "sha512-pHhpQdr1UPFpt+zFfnPazhulaZYCUqeIcPpJViYoq9R+D/yw4fjE+CtnsnKzPYm0ddUbeXUzjGVGIRVgPDCk4Q==", + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "5.22.0" + } + }, "node_modules/@react-aria/breadcrumbs": { "version": "3.5.13", "resolved": "https://registry.npmjs.org/@react-aria/breadcrumbs/-/breadcrumbs-3.5.13.tgz", @@ -9125,6 +9171,25 @@ "integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==", "license": "MIT" }, + "node_modules/prisma": { + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.22.0.tgz", + "integrity": "sha512-vtpjW3XuYCSnMsNVBjLMNkTj6OZbudcPPTPYHqX0CJfpcdWciI1dM8uHETwmDxxiqEwCIE6WvXucWUetJgfu/A==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/engines": "5.22.0" + }, + "bin": { + "prisma": "build/index.js" + }, + "engines": { + "node": ">=16.13" + }, + "optionalDependencies": { + "fsevents": "2.3.3" + } + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", diff --git a/package.json b/package.json index 0b8d7dd..e01c71a 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "next-client-cookies": "^2.0.0", "next-themes": "^0.3.0", "postcss": "8.4.47", + "prisma": "^5.22.0", "react": "18.3.1", "react-dom": "18.3.1", "swr": "^2.2.5", diff --git a/prisma/migrations/20241109035411_add_ratings/migration.sql b/prisma/migrations/20241109035411_add_ratings/migration.sql new file mode 100644 index 0000000..48546e8 --- /dev/null +++ b/prisma/migrations/20241109035411_add_ratings/migration.sql @@ -0,0 +1,29 @@ +-- AlterTable +ALTER TABLE "Course" ADD COLUMN "avgRating" DOUBLE PRECISION; + +-- AlterTable +ALTER TABLE "Faculty" ADD COLUMN "avgRating" DOUBLE PRECISION; + +-- CreateTable +CREATE TABLE "Rating" ( + "id" SERIAL NOT NULL, + "courseID" INTEGER NOT NULL, + "facultyID" INTEGER NOT NULL, + "overallRating" INTEGER, + "quality" INTEGER, + "difficulty" INTEGER, + "takeAgain" BOOLEAN, + "review" TEXT, + "grade" TEXT, + "forCredit" BOOLEAN, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Rating_pkey" PRIMARY KEY ("id") +); + +-- AddForeignKey +ALTER TABLE "Rating" ADD CONSTRAINT "Rating_facultyID_fkey" FOREIGN KEY ("facultyID") REFERENCES "Faculty"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Rating" ADD CONSTRAINT "Rating_courseID_fkey" FOREIGN KEY ("courseID") REFERENCES "Course"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/migrations/20241109040421_add_ratings1/migration.sql b/prisma/migrations/20241109040421_add_ratings1/migration.sql new file mode 100644 index 0000000..9d1f188 --- /dev/null +++ b/prisma/migrations/20241109040421_add_ratings1/migration.sql @@ -0,0 +1,5 @@ +-- AlterTable +ALTER TABLE "Rating" ADD COLUMN "userId" INTEGER; + +-- AddForeignKey +ALTER TABLE "Rating" ADD CONSTRAINT "Rating_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 98731af..b217798 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -1,6 +1,6 @@ generator client { provider = "prisma-client-js" - previewFeatures = ["fullTextSearch", "relationJoins", "strictUndefinedChecks","fullTextIndex"] + previewFeatures = ["fullTextIndex", "fullTextSearch", "relationJoins", "strictUndefinedChecks"] } datasource db { @@ -9,20 +9,21 @@ datasource db { } model User { - id Int @id @default(autoincrement()) - uuid String @unique - email String @unique - name String? - plans CoursePlan[] + 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? - courses Course[] - User User? @relation(fields: [userId], references: [id]) year String + User User? @relation(fields: [userId], references: [id]) + courses Course[] @relation("CourseToCoursePlan") } model Course { @@ -41,11 +42,14 @@ model Course { seatsAvailable Int facultyId Int facultyMeetId Int + year String + avgRating Float? instructor Faculty @relation(fields: [facultyId], references: [id]) facultyMeet MeetingsFaculty @relation(fields: [facultyMeetId], references: [id]) + Rating Rating[] sectionAttributes sectionAttribute[] - CoursePlan CoursePlan[] - year String + CoursePlan CoursePlan[] @relation("CourseToCoursePlan") + @@index([subject, courseNumber], map: "subj_course") } @@ -55,8 +59,10 @@ model Faculty { courseReferenceNumber String @unique displayName String emailAddress String - courses Course[] year String + avgRating Float? + courses Course[] + Rating Rating[] } model MeetingsFaculty { @@ -64,21 +70,19 @@ model MeetingsFaculty { category String courseReferenceNumber String @unique meetingTimeID Int + year String courses Course[] meetingTimes MeetingTime @relation(fields: [meetingTimeID], references: [id]) - year String } model MeetingTime { id Int @id @default(autoincrement()) - beginTime String building String buildingDescription String room String category String courseReferenceNumber String @unique endDate String - endTime String startDate String hoursWeek Float meetingType String @@ -90,16 +94,37 @@ model MeetingTime { friday Boolean saturday Boolean sunday Boolean - facultyMeet MeetingsFaculty[] year String + beginTime String + endTime String + facultyMeet MeetingsFaculty[] } model sectionAttribute { - id Int @id @default(autoincrement()) + id Int @id @default(autoincrement()) code String description String - courseReferenceNumber String @unique - courseId Int - Course Course? @relation(fields: [courseId], references: [id]) + 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? + quality Int? + difficulty Int? + takeAgain Boolean? + review String? + grade String? + forCredit Boolean? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + course Course @relation(fields: [courseID], references: [id]) + faculty Faculty @relation(fields: [facultyID], references: [id]) + User User? @relation(fields: [userId], references: [id]) + userId Int? } diff --git a/swatscraper b/swatscraper index 32a5d97..6089f91 160000 --- a/swatscraper +++ b/swatscraper @@ -1 +1 @@ -Subproject commit 32a5d975a8cbe906764c553846d220de1f0ed4ab +Subproject commit 6089f9194726570223d8d85018e2a4377925366b