From f3735b47d388624488df5639a3edc5e448289a25 Mon Sep 17 00:00:00 2001 From: Damian <37555910+DCRepublic@users.noreply.github.com> Date: Thu, 31 Oct 2024 14:12:06 -0400 Subject: [PATCH] CoursePlan: add plan support --- app/actions.ts | 18 ++ app/api/course/route.ts | 32 --- app/api/createplan/route.ts | 78 ++++++ app/api/getcourseplans/route.ts | 31 +++ app/api/getplancourses/route.ts | 27 ++ app/api/updatePlan/route.ts | 77 ++++++ app/api/user/route.ts | 50 ++++ app/blog/layout.tsx | 13 - app/blog/page.tsx | 9 - app/layout.tsx | 99 +++---- app/page.tsx | 34 ++- app/pricing/layout.tsx | 13 - app/pricing/page.tsx | 9 - components/CourseCard.tsx | 163 ++++++------ components/CreatePlan.tsx | 241 ++++++++++++++++++ components/FullCourseList.tsx | 6 +- components/PlanCard.tsx | 37 +++ components/PlanCardList.tsx | 40 +++ components/Search.tsx | 1 - components/navbar.tsx | 19 +- lib/auth.ts | 2 + package-lock.json | 23 ++ package.json | 1 + .../migration.sql | 3 + .../migration.sql | 15 ++ .../migration.sql | 2 + .../migration.sql | 29 +++ prisma/schema.prisma | 8 +- 28 files changed, 865 insertions(+), 215 deletions(-) create mode 100644 app/actions.ts delete mode 100644 app/api/course/route.ts create mode 100644 app/api/createplan/route.ts create mode 100644 app/api/getcourseplans/route.ts create mode 100644 app/api/getplancourses/route.ts create mode 100644 app/api/updatePlan/route.ts create mode 100644 app/api/user/route.ts delete mode 100644 app/blog/layout.tsx delete mode 100644 app/blog/page.tsx delete mode 100644 app/pricing/layout.tsx delete mode 100644 app/pricing/page.tsx create mode 100644 components/CreatePlan.tsx create mode 100644 components/PlanCard.tsx create mode 100644 components/PlanCardList.tsx create mode 100644 prisma/migrations/20241030150608_course_schema3/migration.sql create mode 100644 prisma/migrations/20241030154811_course_schema2/migration.sql create mode 100644 prisma/migrations/20241030155140_course_schema2/migration.sql create mode 100644 prisma/migrations/20241030180417_course_schema2/migration.sql diff --git a/app/actions.ts b/app/actions.ts new file mode 100644 index 0000000..af32f63 --- /dev/null +++ b/app/actions.ts @@ -0,0 +1,18 @@ +"use server"; + +import { cookies } from "next/headers"; + +export async function setPlanCookie(plan: string) { + //@ts-ignore + (await cookies()).set("plan", plan); +} + +export async function getPlanCookie() { + const cookieStore = await cookies(); + //@ts-ignore + const plan = await cookieStore.get("plan"); + + const out = plan ? plan.value : undefined; + + return out; +} diff --git a/app/api/course/route.ts b/app/api/course/route.ts deleted file mode 100644 index f041c7c..0000000 --- a/app/api/course/route.ts +++ /dev/null @@ -1,32 +0,0 @@ -// api/test.ts - -import { NextResponse, NextRequest } from "next/server"; - -import prisma from "@/lib/prisma"; - -export async function GET(request: NextRequest) { - const courses = await prisma.course.findMany({ - include: { - sectionAttributes: true, - facultyMeet: { - include: { - meetingTimes: true, - }, - }, - instructor: true, - }, - orderBy: { - courseTitle: "asc", - }, - }); - - return NextResponse.json(courses, { status: 200 }); -} - -// To handle a POST request to /api -/* -export async function POST(request: NextRequest) { - - return NextResponse.json(output, { status: 200 }); -} -*/ diff --git a/app/api/createplan/route.ts b/app/api/createplan/route.ts new file mode 100644 index 0000000..b8708f3 --- /dev/null +++ b/app/api/createplan/route.ts @@ -0,0 +1,78 @@ +// api/test.ts +import { NextResponse, NextRequest } from "next/server"; +import prisma from "@/lib/prisma"; +import { getServerSession } from "next-auth/next"; +import { auth } from "@/lib/auth"; + +export async function POST(request: NextRequest) { + const data = await request.json(); + const session = await auth(); + + //@ts-ignore + const user = await prisma.user.findUnique({ + where: { + //@ts-ignore + uuid: session?.user?.id, + }, + }); + + const plan = await prisma.coursePlan.create({ + data: { + //@ts-ignore + name: data.planName, + year: "F2024", + User: { + connect: { + id: user?.id, + }, + }, + }, + }); + + return NextResponse.json(plan, { status: 200 }); +} + +export async function GET(request: NextRequest) { + const session = await auth(); + const user = await prisma.user.findUnique({ + where: { + //@ts-ignore + uuid: session?.user?.id, + }, + }); + const courses = await prisma.coursePlan.findMany({ + where: { + User: { + //@ts-ignore + id: user.id, + }, + }, + include: { + courses: true, + }, + }); + //console.log(plans); + return NextResponse.json(courses, { status: 200 }); +} + +export async function DELETE(request: NextRequest) { + const data = await request.json(); + + //@ts-ignore + const plan = await prisma.coursePlan.delete({ + where: { + //@ts-ignore + id: parseInt(data.planId), + }, + }); + + return NextResponse.json(plan, { status: 200 }); +} + +// To handle a POST request to /api +/* +export async function POST(request: NextRequest) { + + return NextResponse.json(output, { status: 200 }); +} +*/ diff --git a/app/api/getcourseplans/route.ts b/app/api/getcourseplans/route.ts new file mode 100644 index 0000000..663d62f --- /dev/null +++ b/app/api/getcourseplans/route.ts @@ -0,0 +1,31 @@ +import { NextResponse, NextRequest } from "next/server"; +import prisma from "@/lib/prisma"; +import { auth } from "@/lib/auth"; + +export async function GET(request: NextRequest) { + const session = await auth(); + const user = await prisma.user.findUnique({ + where: { + //@ts-ignore + uuid: session?.user?.id, + }, + }); + let courses: any; + if (user) { + courses = await prisma.coursePlan.findMany({ + where: { + User: { + //@ts-ignore + id: user.id, + }, + }, + include: { + courses: true, + }, + }); + } else { + courses = null; + } + //console.log(plans); + return NextResponse.json(courses, { status: 200 }); +} diff --git a/app/api/getplancourses/route.ts b/app/api/getplancourses/route.ts new file mode 100644 index 0000000..684e5de --- /dev/null +++ b/app/api/getplancourses/route.ts @@ -0,0 +1,27 @@ +// api/test.ts +import { NextResponse, NextRequest } from "next/server"; +import prisma from "@/lib/prisma"; +import { getServerSession } from "next-auth/next"; +import { auth } from "@/lib/auth"; +import { getPlanCookie } from "@/app/actions"; + +export async function GET(request: NextRequest) { + let planCookie: any = await getPlanCookie(); + const session = await auth(); + + let courses = await prisma.coursePlan.findMany({ + where: { + AND: { + User: { + //@ts-ignore + uuid: session?.user.id, + }, + id: parseInt(planCookie), + }, + }, + include: { + courses: true, + }, + }); + return NextResponse.json(courses, { status: 200 }); +} diff --git a/app/api/updatePlan/route.ts b/app/api/updatePlan/route.ts new file mode 100644 index 0000000..4daa4c9 --- /dev/null +++ b/app/api/updatePlan/route.ts @@ -0,0 +1,77 @@ +// api/test.ts +import { NextResponse, NextRequest } from "next/server"; +import prisma from "@/lib/prisma"; +import { getServerSession } from "next-auth/next"; +import { auth } from "@/lib/auth"; +import { getPlanCookie } from "@/app/actions"; + +export async function POST(request: NextRequest) { + const course = await request.json(); + const planId = await getPlanCookie(); + + if (planId) { + let plan: any; + + if (planId) { + plan = await prisma.coursePlan.update({ + where: { + id: parseInt(planId), + }, + data: { + courses: { + connect: { + id: course.course.id, + }, + }, + }, + }); + } + } + + return NextResponse.json(planId, { status: 200 }); +} + +export async function GET(request: NextRequest) { + const session = await auth(); + const user = await prisma.user.findUnique({ + where: { + //@ts-ignore + uuid: session?.user?.id, + }, + }); + const courses = await prisma.coursePlan.findMany({ + where: { + User: { + //@ts-ignore + id: user.id, + }, + }, + include: { + courses: true, + }, + }); + console.log(courses); + return NextResponse.json(courses, { status: 200 }); +} + +export async function DELETE(request: NextRequest) { + const data = await request.json(); + + //@ts-ignore + const plan = await prisma.coursePlan.delete({ + where: { + //@ts-ignore + id: parseInt(data.planId), + }, + }); + + return NextResponse.json(plan, { status: 200 }); +} + +// To handle a POST request to /api +/* +export async function POST(request: NextRequest) { + + return NextResponse.json(output, { status: 200 }); +} +*/ diff --git a/app/api/user/route.ts b/app/api/user/route.ts new file mode 100644 index 0000000..97f400a --- /dev/null +++ b/app/api/user/route.ts @@ -0,0 +1,50 @@ +// api/test.ts +import { NextResponse, NextRequest } from "next/server"; +import prisma from "@/lib/prisma"; + +export async function POST(request: NextRequest) { + const data = await request.json(); + //@ts-ignore + const session = data?.session; + + const user = await prisma.user.upsert({ + where: { + //@ts-ignore + uuid: session?.user?.id, + }, + update: { + //@ts-ignore + uuid: session?.user?.id, + email: session?.user?.email, + name: session?.user?.name, + }, + create: { + //@ts-ignore + uuid: session?.user?.id, + email: session?.user?.email, + name: session?.user?.name, + }, + }); + + return NextResponse.json(user, { status: 200 }); +} + +export async function GET(request: NextRequest) { + /* + const session = data.session; + const user = await prisma.user.findUnique({ + where: { + //@ts-ignore + id: session?.user?.id, + }, + }); +*/ + return NextResponse.json("HI", { status: 200 }); +} +// To handle a POST request to /api +/* +export async function POST(request: NextRequest) { + + return NextResponse.json(output, { status: 200 }); +} +*/ diff --git a/app/blog/layout.tsx b/app/blog/layout.tsx deleted file mode 100644 index 911d0bc..0000000 --- a/app/blog/layout.tsx +++ /dev/null @@ -1,13 +0,0 @@ -export default function BlogLayout({ - children, -}: { - children: React.ReactNode; -}) { - return ( -
-
- {children} -
-
- ); -} diff --git a/app/blog/page.tsx b/app/blog/page.tsx deleted file mode 100644 index c6d0c65..0000000 --- a/app/blog/page.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { title } from "@/components/primitives"; - -export default function BlogPage() { - return ( -
-

Blog

-
- ); -} diff --git a/app/layout.tsx b/app/layout.tsx index 78c2672..0d66156 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -5,6 +5,7 @@ import clsx from "clsx"; import { Providers } from "./providers"; import { NextAuthProvider } from "../components/providers/NextAuthProvider"; +import { CookiesProvider } from "next-client-cookies/server"; import { siteConfig } from "@/config/site"; import { fontSans } from "@/config/fonts"; @@ -34,57 +35,59 @@ export default function RootLayout({ children: React.ReactNode; }) { return ( - - - - - + + + + -
- -
- {children} -
- -
-
- - -
+ + + + + + + ); } diff --git a/app/page.tsx b/app/page.tsx index 5502bd4..af092e4 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -4,6 +4,11 @@ import { Skeleton } from "@nextui-org/skeleton"; import Search from "@/components/Search"; import { FullCourseList } from "@/components/FullCourseList"; +import CreatePlan from "@/components/CreatePlan"; +import { auth } from "@/lib/auth"; +import prisma from "@/lib/prisma"; +import { Course, CoursePlan } from "@prisma/client"; +import { getPlanCookie } from "@/app/actions"; export default async function Page(props: { searchParams?: Promise<{ @@ -25,16 +30,35 @@ export default async function Page(props: { ); + homePageProps["createPlan"] = ( + + } + > + + + ); + return ; // return with no events } - async function Home(props: any) { return ( <> -
- -
- {props.fullCourseList} +
+
+
+
+ +
+
+ {props.fullCourseList} +
+
+
+
+ +
); diff --git a/app/pricing/layout.tsx b/app/pricing/layout.tsx deleted file mode 100644 index dc3db6a..0000000 --- a/app/pricing/layout.tsx +++ /dev/null @@ -1,13 +0,0 @@ -export default function PricingLayout({ - children, -}: { - children: React.ReactNode; -}) { - return ( -
-
- {children} -
-
- ); -} diff --git a/app/pricing/page.tsx b/app/pricing/page.tsx deleted file mode 100644 index 42e2333..0000000 --- a/app/pricing/page.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { title } from "@/components/primitives"; - -export default function PricingPage() { - return ( -
-

Pricing

-
- ); -} diff --git a/components/CourseCard.tsx b/components/CourseCard.tsx index 53d1fb3..96cd80e 100644 --- a/components/CourseCard.tsx +++ b/components/CourseCard.tsx @@ -8,14 +8,16 @@ import { Popover, PopoverTrigger, PopoverContent, + Button, } from "@nextui-org/react"; import { tv } from "tailwind-variants"; import { InstructorCard } from "./InstructorCard"; - +import AddIcon from "@mui/icons-material/Add"; +import axios from "axios"; export const card = tv({ slots: { - base: "bg-light_foreground min-h-32 max-h-42 w-[98%] rounded-sm scroll-none drop-shadow-lg", + base: "bg-light_foreground min-h-32 max-h-32 w-[98%] rounded-sm scroll-none drop-shadow-lg transition-colors", role: "font-bold text-primary ", }, }); @@ -48,87 +50,97 @@ function generateColorFromName(name: string) { return courseColors[hash % courseColors.length]; } +async function updatePlan(course: any) { + console.log("updating"); + await axios + .post("/api/updatePlan", { + course: course, + }) + .then(function (response) { + //console.log(response); + }) + .catch(function (error) { + console.log(error); + }); +} + export default function CourseCard(props: any) { let color = generateColorFromName(props.course.subject); return ( - <> - -
- - - {/* 3 cols 2 rows*/} -
-
- -

- {props.course.subject + - " " + - props.course.courseNumber + - " - " + - props.course.courseTitle} -

- -
-
- -
-
- {props.course.instructor ? ( - - - - - - - - - ) : null} -
+ +
+ + updatePlan(props.course)} + > + {/* 3 cols 2 rows*/} +
+
+

+ {props.course.subject + + " " + + props.course.courseNumber + + " - " + + props.course.courseTitle} +

+
+
+ +
+
+ {props.course.instructor ? ( + + + + + + + + + ) : null} +
-
-
{props.course.creditHours} credit(s)
-
-
- {/*props.course.courseReferenceNumber.replace( +
+
{props.course.creditHours} credit(s)
+
+
+ {/*props.course.courseReferenceNumber.replace( props.course.year, "" )*/} -
+
-
- {/* */} - {props.course.enrollment}/{props.course.maximumEnrollment} -
+
+ {/* */} + {props.course.enrollment}/{props.course.maximumEnrollment} +
-
-
Attributes
- {props.course.sectionAttributes.map((attribute: any) => ( -
- {attribute.code} -
- ))} -
+
+
Attributes
+ {props.course.sectionAttributes.map((attribute: any) => ( +
+ {attribute.code} +
+ ))} +
- {/* + {/*

Availability

@@ -212,9 +224,8 @@ export default function CourseCard(props: any) { ) : null} */} -
- - - +
+ + ); } diff --git a/components/CreatePlan.tsx b/components/CreatePlan.tsx new file mode 100644 index 0000000..d2072bb --- /dev/null +++ b/components/CreatePlan.tsx @@ -0,0 +1,241 @@ +"use client"; +import { + Card, + CardBody, + Divider, + Link, + User, + Popover, + PopoverTrigger, + PopoverContent, + Input, + Button, + Skeleton, +} from "@nextui-org/react"; +import { + Dropdown, + DropdownTrigger, + DropdownMenu, + DropdownItem, + RadioGroup, + Radio, +} from "@nextui-org/react"; +import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown"; +import AddIcon from "@mui/icons-material/Add"; +import DeleteIcon from "@mui/icons-material/Delete"; +import IosShareIcon from "@mui/icons-material/IosShare"; +import { tv } from "tailwind-variants"; +import axios from "axios"; +import { Select, SelectItem } from "@nextui-org/react"; + +import { InstructorCard } from "./InstructorCard"; +import { usePathname } from "next/navigation"; +import { useSession } from "next-auth/react"; +import { useEffect, useState } from "react"; +import useSWR from "swr"; +import { setPlanCookie } from "@/app/actions"; +import { useCookies } from "next-client-cookies"; + +export default function CreatePlan(props: any) { + const cookies = useCookies(); + + const pathname = usePathname(); + const { data: session, status } = useSession(); + const [coursePlanName, setCoursePlanName] = useState(); + const [selectedCoursePlan, setSelectedCoursePlan]: any = useState([]); + + const fetcher = (url: any) => fetch(url).then((r) => r.json()); + const { data, isLoading, error } = useSWR("/api/getplancourses", fetcher, { + refreshInterval: 1000, + }); + + const { + data: coursePlans, + isLoading: coursePlansIsLoading, + error: coursePlansError, + } = useSWR("/api/getcourseplans", fetcher, { + refreshInterval: 1000, + }); + + async function createPlan() { + if (coursePlanName) { + await axios + .post("/api/createplan", { + planName: coursePlanName, + }) + .then(function (response: any) { + setPlanCookie(response.id); + //console.log(response); + }) + .catch(function (error) { + console.log(error); + }); + } + } + async function deletePlan() { + if (cookies.get("plan")) { + axios + .delete("/api/createplan", { + data: { + planId: cookies.get("plan"), + }, + }) + .then(function (response) { + //setPlanCookie("-55"); + //console.log(response); + }) + .catch(function (error) { + console.log(error); + }); + } + } + const handleSelectionChange = (e: any) => { + //console.log(e.target.value); + setSelectedCoursePlan([e.target.value]); + //cookies.set("plan", e.target.value); + setPlanCookie(e.target.value); + }; + + const courseColors = [ + "#D1FAFF", + "#9BD1E5", + "#6A8EAE", + "#57A773", + "#157145", + "#1E2D2F", + "#C57B57", + "#9B489B", + "#4ECDC4", + ]; + + function generateColorFromName(name: string) { + let hash = 0; + + for (let i = 0; i < name.length; i++) { + hash += name.charCodeAt(i); + } + + return courseColors[hash % courseColors.length]; + } + + useEffect(() => { + // Update the document title using the browser API + setSelectedCoursePlan([cookies.get("plan")]); + var objDiv: any = document.getElementById("scrollMe"); + objDiv.scrollTop = objDiv.scrollHeight; + }, [cookies.get("plan")]); + + const CoursesList = () => { + let output: any = []; + if (isLoading) { + return ; + } + if (data && !isLoading) { + for (let i = 0; i < data.length; i++) { + if ((data.id = selectedCoursePlan)) { + data[i].courses.map((course: any) => + output.push( + +
+ + + {course.courseTitle} + + + ) + ); + } + } + } + + return output; + }; + + return ( + <> + + +
+
Create a Plan
+
+ { + setCoursePlanName(event.target.value); + }} + /> + +
+ +
+
+ +
or
+ +
+
+ + {/* --------------------------------- or --------------------------- */} + +
Select a Plan
+
+ + + + +
+ +
+ +
+
+ + ); +} diff --git a/components/FullCourseList.tsx b/components/FullCourseList.tsx index 4b38410..2a04994 100644 --- a/components/FullCourseList.tsx +++ b/components/FullCourseList.tsx @@ -1,9 +1,7 @@ import { Course } from "@prisma/client"; - import prisma from "../lib/prisma"; - import CourseCard from "./CourseCard"; - +import { getPlanCookie } from "@/app/actions"; async function getCourses(query: string) { return await prisma.course.findMany({ include: { @@ -57,7 +55,7 @@ export async function FullCourseList({ query }: { query: string }) { return ( <> -
+
{courseList?.map((course: any) => (
diff --git a/components/PlanCard.tsx b/components/PlanCard.tsx new file mode 100644 index 0000000..45e1324 --- /dev/null +++ b/components/PlanCard.tsx @@ -0,0 +1,37 @@ +"use client"; +import { + Card, + CardBody, + Divider, + Link, + User, + Popover, + PopoverTrigger, + PopoverContent, + Button, +} from "@nextui-org/react"; +import { tv } from "tailwind-variants"; + +import { InstructorCard } from "./InstructorCard"; +import AddIcon from "@mui/icons-material/Add"; +import axios from "axios"; +export const card = tv({ + slots: { + base: "bg-light_foreground min-h-32 max-h-32 w-[98%] rounded-sm scroll-none drop-shadow-lg transition-colors", + role: "font-bold text-primary ", + }, +}); + +const { base, role } = card(); + +export default function PlanCard(props: any) { + return ( + +
+ + + {props.course.courseTitle} + + + ); +} diff --git a/components/PlanCardList.tsx b/components/PlanCardList.tsx new file mode 100644 index 0000000..c037251 --- /dev/null +++ b/components/PlanCardList.tsx @@ -0,0 +1,40 @@ +import { Course, CoursePlan } from "@prisma/client"; +import prisma from "../lib/prisma"; +import PlanCard from "./PlanCard"; + +async function getPlans(planId: any) { + const courses = await prisma.course.findMany({ + where: { + id: planId, + }, + }); + /* + const plans = await prisma.coursePlan.findMany({ + where: { + User: { + //@ts-ignore + uuid: session?.user?.id, + }, + }, + include: { + courses: true, + }, + });*/ + return courses; +} + +export async function PlanCardList(planId: any) { + const courses: Course[] = await getPlans(planId); + + return ( + <> +
+ {courses?.map((course: any) => ( +
+ +
+ ))} +
+ + ); +} diff --git a/components/Search.tsx b/components/Search.tsx index bf32545..64b388e 100644 --- a/components/Search.tsx +++ b/components/Search.tsx @@ -11,7 +11,6 @@ export default function Search(props: any) { const handleSearch = useDebouncedCallback((term: string) => { const params = new URLSearchParams(searchParams); - if (term) { params.set("query", term); } else { diff --git a/components/navbar.tsx b/components/navbar.tsx index e9d2a27..bde0f12 100644 --- a/components/navbar.tsx +++ b/components/navbar.tsx @@ -38,6 +38,8 @@ import { title } from "@/components/primitives"; import HomeIcon from "@mui/icons-material/Home"; import SendIcon from "@mui/icons-material/Send"; +import axios from "axios"; + const pages = { Home: { link: "/", image: }, Submit: { link: "/submit", image: }, @@ -58,7 +60,22 @@ export const Navbar = (props: any) => { } else { if (status === "authenticated") { authenticated = true; - loginLink =
signOut()}>Log out
; + axios + .post("/api/user", { + session: session, + }) + .then(function (response) { + //console.log(response); + }) + .catch(function (error) { + console.log(error); + }); + + loginLink = ( +
signOut()}> + Log out +
+ ); // @ts-ignore if (session.user?.role === "admin") { adminDashLink = ( diff --git a/lib/auth.ts b/lib/auth.ts index 452f138..874fe95 100644 --- a/lib/auth.ts +++ b/lib/auth.ts @@ -40,6 +40,8 @@ export const config = { if (session && session.user) { // @ts-ignore session.user.role = token.role; + // @ts-ignore + session.user.id = token.sub; } return session; }, diff --git a/package-lock.json b/package-lock.json index d1e2d48..68e46ea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,6 +33,7 @@ "intl-messageformat": "^10.7.3", "next": "^15.0.1", "next-auth": "^4.24.10", + "next-client-cookies": "^2.0.0", "next-themes": "^0.3.0", "react": "18.3.1", "react-dom": "18.3.1", @@ -7621,6 +7622,15 @@ "url": "https://github.com/sponsors/panva" } }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -8010,6 +8020,19 @@ } } }, + "node_modules/next-client-cookies": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/next-client-cookies/-/next-client-cookies-2.0.0.tgz", + "integrity": "sha512-Ooqt7gqs+Gs1c4Gp1SKVMElzHn291+KenYDy4GZSrmeOiVKHN9QrbLswkjjioF1tenytSKhbu6Bu/JlQ+/HL7w==", + "license": "MIT", + "dependencies": { + "js-cookie": "^3.0.5" + }, + "peerDependencies": { + "next": ">= 15.0.0", + "react": ">= 16.8.0" + } + }, "node_modules/next-themes": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.3.0.tgz", diff --git a/package.json b/package.json index 79d7996..46745b7 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "intl-messageformat": "^10.7.3", "next": "^15.0.1", "next-auth": "^4.24.10", + "next-client-cookies": "^2.0.0", "next-themes": "^0.3.0", "react": "18.3.1", "react-dom": "18.3.1", diff --git a/prisma/migrations/20241030150608_course_schema3/migration.sql b/prisma/migrations/20241030150608_course_schema3/migration.sql new file mode 100644 index 0000000..06a273b --- /dev/null +++ b/prisma/migrations/20241030150608_course_schema3/migration.sql @@ -0,0 +1,3 @@ +-- AlterTable +ALTER TABLE "User" ALTER COLUMN "id" DROP DEFAULT; +DROP SEQUENCE "User_id_seq"; diff --git a/prisma/migrations/20241030154811_course_schema2/migration.sql b/prisma/migrations/20241030154811_course_schema2/migration.sql new file mode 100644 index 0000000..7df6bc6 --- /dev/null +++ b/prisma/migrations/20241030154811_course_schema2/migration.sql @@ -0,0 +1,15 @@ +/* + Warnings: + + - A unique constraint covering the columns `[uuid]` on the table `User` will be added. If there are existing duplicate values, this will fail. + - Added the required column `uuid` to the `User` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +CREATE SEQUENCE user_id_seq; +ALTER TABLE "User" ADD COLUMN "uuid" INTEGER NOT NULL, +ALTER COLUMN "id" SET DEFAULT nextval('user_id_seq'); +ALTER SEQUENCE user_id_seq OWNED BY "User"."id"; + +-- CreateIndex +CREATE UNIQUE INDEX "User_uuid_key" ON "User"("uuid"); diff --git a/prisma/migrations/20241030155140_course_schema2/migration.sql b/prisma/migrations/20241030155140_course_schema2/migration.sql new file mode 100644 index 0000000..72ddf4d --- /dev/null +++ b/prisma/migrations/20241030155140_course_schema2/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "User" ALTER COLUMN "uuid" SET DATA TYPE TEXT; diff --git a/prisma/migrations/20241030180417_course_schema2/migration.sql b/prisma/migrations/20241030180417_course_schema2/migration.sql new file mode 100644 index 0000000..0ff0e48 --- /dev/null +++ b/prisma/migrations/20241030180417_course_schema2/migration.sql @@ -0,0 +1,29 @@ +/* + Warnings: + + - You are about to drop the column `coursePlanId` on the `Course` table. All the data in the column will be lost. + +*/ +-- DropForeignKey +ALTER TABLE "Course" DROP CONSTRAINT "Course_coursePlanId_fkey"; + +-- AlterTable +ALTER TABLE "Course" DROP COLUMN "coursePlanId"; + +-- CreateTable +CREATE TABLE "_CourseToCoursePlan" ( + "A" INTEGER NOT NULL, + "B" INTEGER NOT NULL +); + +-- CreateIndex +CREATE UNIQUE INDEX "_CourseToCoursePlan_AB_unique" ON "_CourseToCoursePlan"("A", "B"); + +-- CreateIndex +CREATE INDEX "_CourseToCoursePlan_B_index" ON "_CourseToCoursePlan"("B"); + +-- AddForeignKey +ALTER TABLE "_CourseToCoursePlan" ADD CONSTRAINT "_CourseToCoursePlan_A_fkey" FOREIGN KEY ("A") REFERENCES "Course"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "_CourseToCoursePlan" ADD CONSTRAINT "_CourseToCoursePlan_B_fkey" FOREIGN KEY ("B") REFERENCES "CoursePlan"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index e162af5..df3b374 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -1,6 +1,6 @@ generator client { - provider = "prisma-client-js" - previewFeatures = ["fullTextSearch"] + provider = "prisma-client-js" + previewFeatures = ["fullTextSearch", "relationJoins"] } datasource db { @@ -10,6 +10,7 @@ datasource db { model User { id Int @id @default(autoincrement()) + uuid String @unique email String @unique name String? plans CoursePlan[] @@ -43,8 +44,7 @@ model Course { instructor Faculty @relation(fields: [facultyId], references: [id]) facultyMeet MeetingsFaculty @relation(fields: [facultyMeetId], references: [id]) sectionAttributes sectionAttribute[] - CoursePlan CoursePlan? @relation(fields: [coursePlanId], references: [id]) - coursePlanId Int? + CoursePlan CoursePlan[] year String }