From af51184b0248d544193968e759d05cd11d766f05 Mon Sep 17 00:00:00 2001 From: aravk Date: Thu, 26 Oct 2023 18:47:42 -0500 Subject: [PATCH 1/4] Only send relevant info about courses to frontend --- src/components/planner/useGetCourseInfo.ts | 129 ++------------------- src/server/trpc/router/courseCache.ts | 38 +++++- src/server/trpc/router/courses.ts | 25 ---- src/server/trpc/router/validator.ts | 2 +- 4 files changed, 47 insertions(+), 147 deletions(-) diff --git a/src/components/planner/useGetCourseInfo.ts b/src/components/planner/useGetCourseInfo.ts index 135339324..2d8216b1d 100644 --- a/src/components/planner/useGetCourseInfo.ts +++ b/src/components/planner/useGetCourseInfo.ts @@ -1,5 +1,3 @@ -import { useMemo } from 'react'; - import { RouterOutputs, trpc } from '@/utils/trpc'; // TODO: Change implementation @@ -7,9 +5,6 @@ import { RouterOutputs, trpc } from '@/utils/trpc'; const useGetCourseInfo = ( courseCode: string, ): { - prereqs: string[]; - coreqs: string[]; - co_or_pre: string[]; title?: string; description?: string; } => { @@ -18,136 +13,30 @@ const useGetCourseInfo = ( cacheTime: Infinity, refetchOnWindowFocus: false, }); - const { prereqs, coreqs, co_or_pre, title } = useMemo( - () => (data ? getPrereqs(data, courseCode) : { prereqs: [], coreqs: [], co_or_pre: [] }), - [data, courseCode], - ); + if (data === undefined) return {}; + const title = getTitle(data, courseCode); // Get course description - const description = useMemo(() => { - const courseInfo = data?.find( - (course) => course.subject_prefix + ' ' + course.course_number === courseCode, - ); - - return courseInfo ? courseInfo.description : ''; - }, [data, courseCode]); + const courseInfo = data?.find( + (course) => course.subject_prefix + ' ' + course.course_number === courseCode, + ); + const description = courseInfo ? courseInfo.description : ''; - return { prereqs, coreqs, co_or_pre, title, description }; + return { title, description }; }; export default useGetCourseInfo; type CourseData = RouterOutputs['courses']['publicGetAllCourses']; -const getPrereqs = ( - courseData: CourseData, - courseCode: string, -): { prereqs: string[]; coreqs: string[]; co_or_pre: string[]; title?: string } => { - const prereqs: string[] = []; - const coreqs: string[] = []; - const co_or_pre: string[] = []; - +const getTitle = (courseData: CourseData, courseCode: string): string | undefined => { let title; courseData?.find(function (cNum) { if (cNum.subject_prefix + ' ' + cNum.course_number === courseCode) { title = cNum.title; - - cNum.prerequisites?.options.map((elem) => { - if (elem.type !== 'course' && elem.type !== 'other' && elem.options) { - elem.options.map((elem2) => { - if (elem2.type !== 'course' && elem2.type !== 'other') { - elem2.options?.map((elem3) => { - courseData?.map((elem4) => { - if (elem4.id === elem3.class_reference) { - prereqs.push(elem4.subject_prefix + ' ' + elem4.course_number); - } - }); - }); - } else if (elem2.type === 'other') { - prereqs.push(elem2.description || ''); - } else { - courseData?.map((elem4) => { - if (elem4.id === elem2.class_reference) { - prereqs.push(elem4.subject_prefix + ' ' + elem4.course_number); - } - }); - } - }); - } else if (elem.type === 'other') { - prereqs.push(elem.description || ''); - } else { - courseData?.map((elem4) => { - if (elem4.id === elem.class_reference) { - prereqs.push(elem4.subject_prefix + ' ' + elem4.course_number); - } - }); - } - }); - cNum.corequisites?.options.map((elem) => { - if (elem.type !== 'course' && elem.type !== 'other' && elem.options) { - elem.options.map((elem2) => { - if (elem2.type !== 'course' && elem2.type !== 'other') { - elem2.options?.map((elem3) => { - courseData?.map((elem4) => { - if (elem4.id === elem3.class_reference) { - coreqs.push(elem4.subject_prefix + ' ' + elem4.course_number); - } - }); - }); - } else if (elem2.type === 'other') { - coreqs.push(elem2.description || ''); - } else { - courseData?.map((elem4) => { - if (elem4.id === elem2.class_reference) { - coreqs.push(elem4.subject_prefix + ' ' + elem4.course_number); - } - }); - } - }); - } else if (elem.type === 'other') { - coreqs.push(elem.description || ''); - } else { - courseData?.map((elem4) => { - if (elem4.id === elem.class_reference) { - coreqs.push(elem4.subject_prefix + ' ' + elem4.course_number); - } - }); - } - }); - cNum.co_or_pre_requisites?.options.map((elem) => { - if (elem.type !== 'course' && elem.type !== 'other' && elem.options) { - elem.options.map((elem2) => { - if (elem2.type !== 'course' && elem2.type !== 'other') { - elem2.options?.map((elem3) => { - courseData?.map((elem4) => { - if (elem4.id === elem3.class_reference) { - co_or_pre.push(elem4.subject_prefix + ' ' + elem4.course_number); - } - }); - }); - } else if (elem2.type === 'other') { - co_or_pre.push(elem2.description || ''); - } else { - courseData?.map((elem4) => { - if (elem4.id === elem2.class_reference) { - co_or_pre.push(elem4.subject_prefix + ' ' + elem4.course_number); - } - }); - } - }); - } else if (elem.type === 'other') { - co_or_pre.push(elem.description || ''); - } else { - courseData?.map((elem4) => { - if (elem4.id === elem.class_reference) { - co_or_pre.push(elem4.subject_prefix + ' ' + elem4.course_number); - } - }); - } - }); return courseCode; } }); - return { prereqs, coreqs, co_or_pre, title }; + return title; }; diff --git a/src/server/trpc/router/courseCache.ts b/src/server/trpc/router/courseCache.ts index acf4f7a3e..7c9a904da 100644 --- a/src/server/trpc/router/courseCache.ts +++ b/src/server/trpc/router/courseCache.ts @@ -7,9 +7,18 @@ class CourseCacheError extends Error { name = 'CourseCacheError'; } +interface MinimalCourse { + title: string; + description: string; + subject_prefix: string; + course_number: string; +} + class CourseCache { - private coursesByYear: Map = new Map(); + private coursesByYear: Map = new Map(); + private courseWithReqsByYear: Map = new Map(); private mutex = new Mutex(); + private mutexReqs = new Mutex(); public async getCourses(year: number) { // Acquire lock before success check so if another request is fetching, we don't fetch again. @@ -24,6 +33,7 @@ class CourseCache { return await platformPrisma.courses .findMany({ distinct: ['title', 'course_number', 'subject_prefix'], + select: { title: true, description: true, subject_prefix: true, course_number: true }, }) .then((courses) => { this.coursesByYear.set(year, courses); @@ -36,6 +46,32 @@ class CourseCache { }) .finally(release); } + + public async getCourseReqs(year: number) { + // Acquire lock before success check so if another request is fetching, we don't fetch again. + const release = await this.mutexReqs.acquire(); + if (this.courseWithReqsByYear.has(year)) { + release(); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return this.courseWithReqsByYear.get(year)!; // Must exist at this point + } + + console.info(`Fetching courses for year ${year}...`); + return await platformPrisma.courses + .findMany({ + distinct: ['title', 'course_number', 'subject_prefix'], + }) + .then((courses) => { + this.courseWithReqsByYear.set(year, courses); + return courses; + }) + .catch((err) => { + const message = `Error fetching courses for year ${year}: ${err}`; + console.error(message); + throw new CourseCacheError(message, { cause: err }); + }) + .finally(release); + } } const courseCache = new CourseCache(); diff --git a/src/server/trpc/router/courses.ts b/src/server/trpc/router/courses.ts index d3af6eb85..25eb0811a 100644 --- a/src/server/trpc/router/courses.ts +++ b/src/server/trpc/router/courses.ts @@ -7,29 +7,4 @@ export const coursesRouter = router({ publicGetAllCourses: publicProcedure.query(async () => { return await courseCache.getCourses(new Date().getFullYear()); }), - publicGetSanitizedCourses: publicProcedure.query(async ({ ctx }) => { - const courses = await ctx.platformPrisma.courses.findMany({ - select: { - course_number: true, - subject_prefix: true, - id: true, - prerequisites: true, - corequisites: true, - }, - }); - - const courseMapWithIdKey = new Map(); - const courseMapWithCodeKey = new Map(); - - for (const course of courses) { - courseMapWithCodeKey.set(`${course.subject_prefix} ${course.course_number}`, { - prereq: course.prerequisites, - coreq: course.corequisites, - }); - courseMapWithIdKey.set(course.id, `${course.subject_prefix} ${course.course_number}`); - } - // print the map - - return courseMapWithCodeKey; - }), }); diff --git a/src/server/trpc/router/validator.ts b/src/server/trpc/router/validator.ts index 2110e9c95..fa144710f 100644 --- a/src/server/trpc/router/validator.ts +++ b/src/server/trpc/router/validator.ts @@ -32,7 +32,7 @@ export const validatorRouter = router({ year = Math.min(...planData.semesters.map((sem) => sem.year)); } - const coursesFromAPI: PlatformCourse[] = await courseCache.getCourses(year); + const coursesFromAPI: PlatformCourse[] = await courseCache.getCourseReqs(year); /* sanitizing data from API db. * TODO: Fix this later somehow */ From 709f0d2799be03780cfbf9e9ca6b65f88fc4f427 Mon Sep 17 00:00:00 2001 From: aravk Date: Thu, 26 Oct 2023 19:02:25 -0500 Subject: [PATCH 2/4] Run prettier --- validator/degree_data/2022/Psychology(BS).json | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/validator/degree_data/2022/Psychology(BS).json b/validator/degree_data/2022/Psychology(BS).json index b88a6b245..fa91b1136 100644 --- a/validator/degree_data/2022/Psychology(BS).json +++ b/validator/degree_data/2022/Psychology(BS).json @@ -233,12 +233,7 @@ "metadata": { "id": "Psychology(BS)-29" }, - "accepted_prefixes": [ - "PSY", - "CGS", - "CLDP", - "NSC" - ] + "accepted_prefixes": ["PSY", "CGS", "CLDP", "NSC"] } ] }, From 5b614bee4794dc3d27fc853bc6894ad704c5809a Mon Sep 17 00:00:00 2001 From: aravk Date: Fri, 27 Oct 2023 12:24:25 -0500 Subject: [PATCH 3/4] Fix type issues --- src/components/planner/DegreePlanPDF/DegreePlanPDF.tsx | 6 ------ src/components/planner/Sidebar/RequirementsContainer.tsx | 4 ++-- src/server/trpc/router/courseCache.ts | 2 +- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/src/components/planner/DegreePlanPDF/DegreePlanPDF.tsx b/src/components/planner/DegreePlanPDF/DegreePlanPDF.tsx index 2fc95d874..f211070da 100644 --- a/src/components/planner/DegreePlanPDF/DegreePlanPDF.tsx +++ b/src/components/planner/DegreePlanPDF/DegreePlanPDF.tsx @@ -50,9 +50,7 @@ interface DegreePlanPDFProps { transferCredits: string[]; coursesData: { title: string; - id: string; course_number: string; - prerequisites: Prisma.JsonValue; subject_prefix: string; }[]; } @@ -199,9 +197,7 @@ const convertSemestersToAcademicYears = ( semesters: Semester[], coursesData: { title: string; - id: string; course_number: string; - prerequisites: Prisma.JsonValue; subject_prefix: string; }[], ) => { @@ -283,9 +279,7 @@ const addCoursesToSemester = ( semester: Semester, coursesData: { title: string; - id: string; course_number: string; - prerequisites: Prisma.JsonValue; subject_prefix: string; }[], ) => { diff --git a/src/components/planner/Sidebar/RequirementsContainer.tsx b/src/components/planner/Sidebar/RequirementsContainer.tsx index b1cb2603e..05314d8ba 100644 --- a/src/components/planner/Sidebar/RequirementsContainer.tsx +++ b/src/components/planner/Sidebar/RequirementsContainer.tsx @@ -4,7 +4,7 @@ import { useState, useEffect } from 'react'; import useSearch from '@/components/search/search'; import { trpc } from '@/utils/trpc'; -import { courses as Course } from 'prisma/generated/platform'; +import { MinimalCourse } from '@server/trpc/router/courseCache'; import Accordion from './Accordion'; import { RecursiveRequirement } from './RecursiveRequirement'; @@ -76,7 +76,7 @@ function RequirementContainerHeader({ const getRequirementGroup = ( degreeRequirement: RequirementGroupTypes, - allCourses: Course[] | undefined, + allCourses: MinimalCourse[] | undefined, ): { name: string; progress: { value: number; max: number; unit: string }; diff --git a/src/server/trpc/router/courseCache.ts b/src/server/trpc/router/courseCache.ts index 7c9a904da..50586b59e 100644 --- a/src/server/trpc/router/courseCache.ts +++ b/src/server/trpc/router/courseCache.ts @@ -7,7 +7,7 @@ class CourseCacheError extends Error { name = 'CourseCacheError'; } -interface MinimalCourse { +export interface MinimalCourse { title: string; description: string; subject_prefix: string; From 964e0c9c3a6979bf3d1c1f9775053b8b55e146dc Mon Sep 17 00:00:00 2001 From: aravk Date: Mon, 30 Oct 2023 15:24:53 -0500 Subject: [PATCH 4/4] Run eslint --fix --- src/components/planner/DegreePlanPDF/DegreePlanPDF.tsx | 2 -- src/server/trpc/router/courses.ts | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/components/planner/DegreePlanPDF/DegreePlanPDF.tsx b/src/components/planner/DegreePlanPDF/DegreePlanPDF.tsx index f211070da..1c7f42343 100644 --- a/src/components/planner/DegreePlanPDF/DegreePlanPDF.tsx +++ b/src/components/planner/DegreePlanPDF/DegreePlanPDF.tsx @@ -9,8 +9,6 @@ import Header from './Header'; import { Semester } from '../types'; import { customCourseSort } from '../utils'; -import type { Prisma } from '@prisma/client'; - Font.register({ family: 'Inter', fonts: [ diff --git a/src/server/trpc/router/courses.ts b/src/server/trpc/router/courses.ts index 25eb0811a..756d74fb8 100644 --- a/src/server/trpc/router/courses.ts +++ b/src/server/trpc/router/courses.ts @@ -1,5 +1,3 @@ -import { Prisma } from '@prisma/client'; - import { courseCache } from './courseCache'; import { router, publicProcedure } from '../trpc';