diff --git a/package-lock.json b/package-lock.json
index a8785022d..0d40458d5 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -30,6 +30,7 @@
"@trpc/react-query": "^10.36.0",
"@trpc/server": "^10.36.0",
"@vercel/analytics": "^0.1.11",
+ "async-mutex": "^0.4.0",
"axios": "^1.3.4",
"eslint": "^8.8.0",
"eslint-plugin-unused-imports": "^2.0.0",
@@ -45,7 +46,6 @@
"prettier-eslint": "^13.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
- "react-hotkeys": "^2.0.0",
"react-hotkeys-hook": "^4.4.1",
"react-loading-skeleton": "^3.3.1",
"react-toastify": "^9.1.1",
@@ -4827,8 +4827,6 @@
"version": "8.12.0",
"dev": true,
"license": "MIT",
- "optional": true,
- "peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
@@ -4843,9 +4841,7 @@
"node_modules/ajv-formats/node_modules/json-schema-traverse": {
"version": "1.0.0",
"dev": true,
- "license": "MIT",
- "optional": true,
- "peer": true
+ "license": "MIT"
},
"node_modules/ajv-keywords": {
"version": "3.5.2",
@@ -5139,6 +5135,14 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/async-mutex": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.4.0.tgz",
+ "integrity": "sha512-eJFZ1YhRR8UN8eBLoNzcDPcy/jqjsg6I1AP+KvWQX80BqOSW1oJPJXDylPUEeMr2ZQvHgnQ//Lp6f3RQ1zI7HA==",
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
"node_modules/asynckit": {
"version": "0.4.0",
"license": "MIT"
@@ -13856,17 +13860,6 @@
"react": ">=16.0.0"
}
},
- "node_modules/react-hotkeys": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/react-hotkeys/-/react-hotkeys-2.0.0.tgz",
- "integrity": "sha512-3n3OU8vLX/pfcJrR3xJ1zlww6KS1kEJt0Whxc4FiGV+MJrQ1mYSYI3qS/11d2MJDFm8IhOXMTFQirfu6AVOF6Q==",
- "dependencies": {
- "prop-types": "^15.6.1"
- },
- "peerDependencies": {
- "react": ">= 0.14.0"
- }
- },
"node_modules/react-hotkeys-hook": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/react-hotkeys-hook/-/react-hotkeys-hook-4.4.1.tgz",
@@ -20329,13 +20322,13 @@
"ajv-formats": {
"version": "2.1.1",
"dev": true,
- "requires": {},
+ "requires": {
+ "ajv": "^8.0.0"
+ },
"dependencies": {
"ajv": {
"version": "8.12.0",
"dev": true,
- "optional": true,
- "peer": true,
"requires": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
@@ -20345,9 +20338,7 @@
},
"json-schema-traverse": {
"version": "1.0.0",
- "dev": true,
- "optional": true,
- "peer": true
+ "dev": true
}
}
},
@@ -20522,6 +20513,14 @@
"version": "3.2.4",
"dev": true
},
+ "async-mutex": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.4.0.tgz",
+ "integrity": "sha512-eJFZ1YhRR8UN8eBLoNzcDPcy/jqjsg6I1AP+KvWQX80BqOSW1oJPJXDylPUEeMr2ZQvHgnQ//Lp6f3RQ1zI7HA==",
+ "requires": {
+ "tslib": "^2.4.0"
+ }
+ },
"asynckit": {
"version": "0.4.0"
},
@@ -26109,14 +26108,6 @@
"prop-types": "^15.7.2"
}
},
- "react-hotkeys": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/react-hotkeys/-/react-hotkeys-2.0.0.tgz",
- "integrity": "sha512-3n3OU8vLX/pfcJrR3xJ1zlww6KS1kEJt0Whxc4FiGV+MJrQ1mYSYI3qS/11d2MJDFm8IhOXMTFQirfu6AVOF6Q==",
- "requires": {
- "prop-types": "^15.6.1"
- }
- },
"react-hotkeys-hook": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/react-hotkeys-hook/-/react-hotkeys-hook-4.4.1.tgz",
@@ -27201,6 +27192,7 @@
"normalize-path": "^3.0.0",
"object-hash": "^3.0.0",
"picocolors": "^1.0.0",
+ "postcss": "^8.4.18",
"postcss-import": "^14.1.0",
"postcss-js": "^4.0.0",
"postcss-load-config": "^3.1.4",
diff --git a/package.json b/package.json
index 54c75564e..004b78a8d 100644
--- a/package.json
+++ b/package.json
@@ -3,6 +3,7 @@
"version": "0.1.0",
"private": true,
"scripts": {
+ "bootstrap": "npm run prisma:generate && npx prisma migrate dev && npm run dev",
"dev": "next dev",
"prisma:generate": "prisma generate --schema prisma/platform.prisma && prisma generate --schema prisma/schema.prisma",
"prisma:migrate:deploy": "prisma migrate deploy --schema prisma/schema.prisma",
@@ -44,6 +45,7 @@
"@trpc/react-query": "^10.36.0",
"@trpc/server": "^10.36.0",
"@vercel/analytics": "^0.1.11",
+ "async-mutex": "^0.4.0",
"axios": "^1.3.4",
"eslint": "^8.8.0",
"eslint-plugin-unused-imports": "^2.0.0",
diff --git a/prisma/platform.prisma b/prisma/platform.prisma
index 728910b8a..f6f14602b 100644
--- a/prisma/platform.prisma
+++ b/prisma/platform.prisma
@@ -8,6 +8,223 @@ datasource db {
url = env("PLATFORM_DATABASE_URL")
}
+type CoursesCoOrPreRequisites {
+ name String
+ options CoursesCoOrPreRequisitesOptions[]
+ required Int
+ type String
+}
+
+type CoursesCoOrPreRequisitesOptions {
+ choices CoursesCoOrPreRequisitesOptionsChoices?
+ class_reference String?
+ condition String?
+ description String?
+ max_hours Int?
+ minimum_grade String?
+ name String?
+ options CoursesCoOrPreRequisitesOptionsOptions[]
+ required Int?
+ type String
+}
+
+type CoursesCoOrPreRequisitesOptionsChoices {
+ name String
+ options CoursesCoOrPreRequisitesOptionsChoicesOptions[]
+ required Int
+ type String
+}
+
+type CoursesCoOrPreRequisitesOptionsChoicesOptions {
+ class_reference String?
+ condition String?
+ description String?
+ minimum_grade String?
+ type String
+}
+
+type CoursesCoOrPreRequisitesOptionsOptions {
+ class_reference String?
+ condition String?
+ description String?
+ minimum_grade String?
+ name String?
+ options CoursesCoOrPreRequisitesOptionsOptionsOptions[]
+ required Int?
+ type String
+}
+
+type CoursesCoOrPreRequisitesOptionsOptionsOptions {
+ class_reference String?
+ condition String?
+ description String?
+ major String?
+ minimum_grade String?
+ minor String?
+ type String
+}
+
+type CoursesCorequisites {
+ name String
+ options CoursesCorequisitesOptions[]
+ required Int
+ type String
+}
+
+type CoursesCorequisitesOptions {
+ choices CoursesCorequisitesOptionsChoices?
+ class_reference String?
+ condition String?
+ description String?
+ max_hours Int?
+ minimum_grade String?
+ name String?
+ options CoursesCorequisitesOptionsOptions[]
+ required Int?
+ type String
+}
+
+type CoursesCorequisitesOptionsChoices {
+ name String
+ options CoursesCorequisitesOptionsChoicesOptions[]
+ required Int
+ type String
+}
+
+type CoursesCorequisitesOptionsChoicesOptions {
+ class_reference String?
+ condition String?
+ description String?
+ minimum_grade String?
+ name String?
+ options CoursesCorequisitesOptionsChoicesOptionsOptions[]
+ required Int?
+ type String
+}
+
+type CoursesCorequisitesOptionsChoicesOptionsOptions {
+ class_reference String
+ minimum_grade String
+ type String
+}
+
+type CoursesCorequisitesOptionsOptions {
+ class_reference String?
+ condition String?
+ description String?
+ minimum_grade String?
+ name String?
+ options CoursesCorequisitesOptionsOptionsOptions[]
+ required Int?
+ type String
+}
+
+type CoursesCorequisitesOptionsOptionsOptions {
+ class_reference String?
+ condition String?
+ description String?
+ major String?
+ minimum_grade String?
+ minor String?
+ type String
+}
+
+type CoursesPrerequisites {
+ name String
+ options CoursesPrerequisitesOptions[]
+ required Int
+ type String
+}
+
+type CoursesPrerequisitesOptions {
+ choices CoursesPrerequisitesOptionsChoices?
+ class_reference String?
+ condition String?
+ core_flag String?
+ description String?
+ hours Int?
+ major String?
+ max_hours Int?
+ minimum Float?
+ minimum_grade String?
+ name String?
+ options CoursesPrerequisitesOptionsOptions[]
+ required Int?
+ subset String?
+ type String
+}
+
+type CoursesPrerequisitesOptionsChoices {
+ name String
+ options CoursesPrerequisitesOptionsChoicesOptions[]
+ required Int
+ type String
+}
+
+type CoursesPrerequisitesOptionsChoicesOptions {
+ class_reference String?
+ condition String?
+ description String?
+ minimum_grade String?
+ name String?
+ options CoursesPrerequisitesOptionsChoicesOptionsOptions[]
+ required Int?
+ type String
+}
+
+type CoursesPrerequisitesOptionsChoicesOptionsOptions {
+ class_reference String
+ minimum_grade String
+ type String
+}
+
+type CoursesPrerequisitesOptionsOptions {
+ class_reference String?
+ condition String?
+ core_flag String?
+ description String?
+ granter String?
+ hours Int?
+ major String?
+ minimum_grade String?
+ name String?
+ options CoursesPrerequisitesOptionsOptionsOptions[]
+ required Int?
+ type String
+}
+
+type CoursesPrerequisitesOptionsOptionsOptions {
+ class_reference String?
+ condition String?
+ description String?
+ granter String?
+ major String?
+ minimum_grade String?
+ name String?
+ options CoursesPrerequisitesOptionsOptionsOptionsOptions[]
+ required Int?
+ type String
+}
+
+type CoursesPrerequisitesOptionsOptionsOptionsOptions {
+ class_reference String?
+ condition String?
+ description String?
+ minimum_grade String?
+ name String?
+ options CoursesPrerequisitesOptionsOptionsOptionsOptionsOptions[]
+ required Int?
+ type String
+}
+
+type CoursesPrerequisitesOptionsOptionsOptionsOptionsOptions {
+ class_reference String?
+ condition String?
+ description String?
+ minimum_grade String?
+ type String
+}
+
type ExamsYields {
outcome Json
requirement ExamsYieldsRequirement
@@ -66,31 +283,20 @@ type SectionsAcademicSession {
start_date String
}
-type SectionsAttributes {
- raw_attributes String[]
-}
-
type SectionsMeetings {
- end_date String?
+ end_date String
end_time String
location SectionsMeetingsLocation
meeting_days String[]
- /// Could not determine type: the field only had null or empty values in the sample set.
- modality Json?
+ modality String
start_date String
start_time String
}
type SectionsMeetingsLocation {
- building String?
+ building String
map_uri String
- room String?
-}
-
-type SectionsSectionCorequisites {
- /// Could not determine type: the field only had null or empty values in the sample set.
- options Json?
- type String
+ room String
}
type SectionsTeachingAssistants {
@@ -101,64 +307,80 @@ type SectionsTeachingAssistants {
}
model courses {
- id String @id @default(auto()) @map("_id") @db.ObjectId
- v Int @map("__v")
+ id String @id @default(auto()) @map("_id") @db.ObjectId
activity_type String
+ /// Could not determine type: the field only had null or empty values in the sample set.
+ attributes Json?
+ catalog_year String
class_level String
- co_or_pre_requisites Json
- corequisites Json
+ co_or_pre_requisites CoursesCoOrPreRequisites?
+ corequisites CoursesCorequisites?
course_number String
credit_hours String
description String
+ enrollment_reqs String
grading String
internal_course_number String
- laboratory_contact_hours String?
- lecture_contact_hours String?
- offering_frequency String?
- prerequisites Json
+ laboratory_contact_hours String
+ lecture_contact_hours String
+ offering_frequency String
+ prerequisites CoursesPrerequisites?
school String
- sections String[] @db.ObjectId
+ sections String[]
subject_prefix String
title String
}
+model degrees {
+ id String @id @default(auto()) @map("_id") @db.ObjectId
+}
+
model exams {
id String @id @default(auto()) @map("_id") @db.ObjectId
+ level String?
name String?
type String
yields ExamsYields[]
+
+ @@index([type], map: "type_1")
+ @@index([name], map: "name_1")
+ @@index([level], map: "level_1")
}
model professors {
- id String @id @default(auto()) @map("_id") @db.ObjectId
- v Int @map("__v")
+ id String @id @default(auto()) @map("_id") @db.ObjectId
email String
first_name String
- image_uri String?
+ image_uri String
last_name String
- office ProfessorsOffice?
+ office ProfessorsOffice
/// Could not determine type: the field only had null or empty values in the sample set.
office_hours Json?
- phone_number String?
- profile_uri String?
- sections String[] @db.ObjectId
+ phone_number String
+ profile_uri String
+ sections String[]
titles String[]
+
+ @@index([first_name, last_name], map: "first_name_1_last_name_1")
}
model sections {
id String @id @default(auto()) @map("_id") @db.ObjectId
- v Int @map("__v")
academic_session SectionsAcademicSession
- attributes SectionsAttributes
+ /// Could not determine type: the field only had null or empty values in the sample set.
+ attributes Json?
core_flags String[]
course_reference String @db.ObjectId
grade_distribution Int[]
instruction_mode String
internal_class_number String
meetings SectionsMeetings[]
- professors String[] @db.ObjectId
- section_corequisites SectionsSectionCorequisites
+ professors String[]
+ /// Could not determine type: the field only had null or empty values in the sample set.
+ section_corequisites Json?
section_number String
- syllabus_uri String?
+ syllabus_uri String
teaching_assistants SectionsTeachingAssistants[]
+
+ @@index([course_reference], map: "course_reference_1")
}
diff --git a/src/components/planner/Sidebar/RequirementsContainer.tsx b/src/components/planner/Sidebar/RequirementsContainer.tsx
index 93741be76..b1cb2603e 100644
--- a/src/components/planner/Sidebar/RequirementsContainer.tsx
+++ b/src/components/planner/Sidebar/RequirementsContainer.tsx
@@ -1,10 +1,10 @@
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
import * as HoverCard from '@radix-ui/react-hover-card';
-import React, { useState } from 'react';
+import { useState, useEffect } from 'react';
import useSearch from '@/components/search/search';
-import { JSONCourse } from '@/data/courses.json';
import { trpc } from '@/utils/trpc';
+import { courses as Course } from 'prisma/generated/platform';
import Accordion from './Accordion';
import { RecursiveRequirement } from './RecursiveRequirement';
@@ -16,7 +16,6 @@ import {
CourseRequirement,
DegreeRequirement,
} from './types';
-import { useSemestersContext } from '../SemesterContext';
import { GetDragIdByCourseAndReq } from '../types';
function RequirementContainerHeader({
@@ -69,7 +68,7 @@ function RequirementContainerHeader({
-
+
);
@@ -77,7 +76,7 @@ function RequirementContainerHeader({
const getRequirementGroup = (
degreeRequirement: RequirementGroupTypes,
- allCourses: JSONCourse[] | undefined,
+ allCourses: Course[] | undefined,
): {
name: string;
progress: { value: number; max: number; unit: string };
@@ -200,15 +199,7 @@ const getRequirementGroup = (
}
};
-export const ProgressComponent2 = ({
- value,
- max,
- unit = 'done',
-}: {
- value: number;
- max: number;
- unit?: string;
-}) => {
+export const ProgressComponent2 = ({ value, max }: { value: number; max: number }) => {
const heh = `${(value * 100) / max}%`;
return (
@@ -269,7 +260,7 @@ export default function RequirementsContainer({
courses,
getCourseItemDragId,
}: RequirementsContainerProps) {
- const [requirementIdx, setRequirementIdx] = React.useState(0);
+ const [requirementIdx, setRequirementIdx] = useState(0);
const q = trpc.courses.publicGetAllCourses.useQuery(undefined, {
staleTime: Infinity,
@@ -280,7 +271,7 @@ export default function RequirementsContainer({
/**
* These hooks manage the carousel state for RequirementsCarousel
*/
- const [carousel, setCarousel] = React.useState(false);
+ const [carousel, setCarousel] = useState(false);
// Note: this logic hides overflow during sliding animation
const [overflow, setOverflow] = useState(false);
@@ -290,8 +281,6 @@ export default function RequirementsContainer({
setCarousel(!carousel);
}
- const { bypasses } = useSemestersContext();
-
return (
{
+ useEffect(() => {
updateQuery('');
- }, [degreeRequirement]);
+ }, [degreeRequirement]); // eslint-disable-line react-hooks/exhaustive-deps
// Put filled requirements first
const sortedResults = [...results]
@@ -410,18 +393,8 @@ function RequirementContainer({
return 0;
})
.slice(0, 100);
-
- const { planId, bypasses, handleAddBypass, handleRemoveBypass } = useSemestersContext();
-
- const hasBypass = bypasses.includes(degreeRequirement.metadata.id.toString());
-
- const handleUpdateBypass = () => {
- hasBypass
- ? handleRemoveBypass({ planId, requirement: degreeRequirement.metadata.id.toString() })
- : handleAddBypass({ planId, requirement: degreeRequirement.metadata.id.toString() });
- };
-
// Handles logic for adding bypass to requirement
+
return (
<>
@@ -442,9 +415,6 @@ function RequirementContainer({
);
})}
- {/* */}
>
);
}
diff --git a/src/components/planner/useGetCourseInfo.ts b/src/components/planner/useGetCourseInfo.ts
index da700cfc3..135339324 100644
--- a/src/components/planner/useGetCourseInfo.ts
+++ b/src/components/planner/useGetCourseInfo.ts
@@ -52,11 +52,11 @@ const getPrereqs = (
if (cNum.subject_prefix + ' ' + cNum.course_number === courseCode) {
title = cNum.title;
- (cNum.prerequisites as Record).options.map((elem: any) => {
+ cNum.prerequisites?.options.map((elem) => {
if (elem.type !== 'course' && elem.type !== 'other' && elem.options) {
- elem.options.map((elem2: any) => {
+ elem.options.map((elem2) => {
if (elem2.type !== 'course' && elem2.type !== 'other') {
- elem2.options?.map((elem3: any) => {
+ elem2.options?.map((elem3) => {
courseData?.map((elem4) => {
if (elem4.id === elem3.class_reference) {
prereqs.push(elem4.subject_prefix + ' ' + elem4.course_number);
@@ -64,7 +64,7 @@ const getPrereqs = (
});
});
} else if (elem2.type === 'other') {
- prereqs.push(elem2.description);
+ prereqs.push(elem2.description || '');
} else {
courseData?.map((elem4) => {
if (elem4.id === elem2.class_reference) {
@@ -74,7 +74,7 @@ const getPrereqs = (
}
});
} else if (elem.type === 'other') {
- prereqs.push(elem.description);
+ prereqs.push(elem.description || '');
} else {
courseData?.map((elem4) => {
if (elem4.id === elem.class_reference) {
@@ -83,11 +83,11 @@ const getPrereqs = (
});
}
});
- (cNum.corequisites as Record).options.map((elem: any) => {
+ cNum.corequisites?.options.map((elem) => {
if (elem.type !== 'course' && elem.type !== 'other' && elem.options) {
- elem.options.map((elem2: any) => {
+ elem.options.map((elem2) => {
if (elem2.type !== 'course' && elem2.type !== 'other') {
- elem2.options?.map((elem3: any) => {
+ elem2.options?.map((elem3) => {
courseData?.map((elem4) => {
if (elem4.id === elem3.class_reference) {
coreqs.push(elem4.subject_prefix + ' ' + elem4.course_number);
@@ -95,7 +95,7 @@ const getPrereqs = (
});
});
} else if (elem2.type === 'other') {
- coreqs.push(elem2.description);
+ coreqs.push(elem2.description || '');
} else {
courseData?.map((elem4) => {
if (elem4.id === elem2.class_reference) {
@@ -105,7 +105,7 @@ const getPrereqs = (
}
});
} else if (elem.type === 'other') {
- coreqs.push(elem.description);
+ coreqs.push(elem.description || '');
} else {
courseData?.map((elem4) => {
if (elem4.id === elem.class_reference) {
@@ -114,11 +114,11 @@ const getPrereqs = (
});
}
});
- (cNum.co_or_pre_requisites as Record).options.map((elem: any) => {
+ cNum.co_or_pre_requisites?.options.map((elem) => {
if (elem.type !== 'course' && elem.type !== 'other' && elem.options) {
- elem.options.map((elem2: any) => {
+ elem.options.map((elem2) => {
if (elem2.type !== 'course' && elem2.type !== 'other') {
- elem2.options?.map((elem3: any) => {
+ elem2.options?.map((elem3) => {
courseData?.map((elem4) => {
if (elem4.id === elem3.class_reference) {
co_or_pre.push(elem4.subject_prefix + ' ' + elem4.course_number);
@@ -126,7 +126,7 @@ const getPrereqs = (
});
});
} else if (elem2.type === 'other') {
- co_or_pre.push(elem2.description);
+ co_or_pre.push(elem2.description || '');
} else {
courseData?.map((elem4) => {
if (elem4.id === elem2.class_reference) {
@@ -136,7 +136,7 @@ const getPrereqs = (
}
});
} else if (elem.type === 'other') {
- co_or_pre.push(elem.description);
+ co_or_pre.push(elem.description || '');
} else {
courseData?.map((elem4) => {
if (elem4.id === elem.class_reference) {
diff --git a/src/server/trpc/router/courseCache.ts b/src/server/trpc/router/courseCache.ts
new file mode 100644
index 000000000..26100155e
--- /dev/null
+++ b/src/server/trpc/router/courseCache.ts
@@ -0,0 +1,46 @@
+import { Mutex } from 'async-mutex';
+
+import { platformPrisma } from '@/server/db/platform_client';
+import { courses as Course } from 'prisma/generated/platform';
+
+class CourseCacheError extends Error {
+ name = 'CourseCacheError';
+}
+
+class CourseCache {
+ private coursesByYear: Map = new Map();
+ private mutex = new Mutex();
+
+ public async getCourses(year: number) {
+ const formattedYear = year.toString().slice(-2);
+ // Acquire lock before success check so if another request is fetching, we don't fetch again.
+ const release = await this.mutex.acquire();
+ if (this.coursesByYear.has(year)) {
+ release();
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ return this.coursesByYear.get(year)!; // Must exist at this point
+ }
+
+ console.info(`Fetching courses for year ${year}...`);
+ return await platformPrisma.courses
+ .findMany({
+ where: {
+ catalog_year: formattedYear,
+ },
+ })
+ .then((courses) => {
+ this.coursesByYear.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();
+
+export { courseCache };
diff --git a/src/server/trpc/router/courses.ts b/src/server/trpc/router/courses.ts
index 1305e6542..d3af6eb85 100644
--- a/src/server/trpc/router/courses.ts
+++ b/src/server/trpc/router/courses.ts
@@ -1,12 +1,11 @@
import { Prisma } from '@prisma/client';
-import courses, { JSONCourse } from '@data/courses.json';
-
+import { courseCache } from './courseCache';
import { router, publicProcedure } from '../trpc';
export const coursesRouter = router({
- publicGetAllCourses: publicProcedure.query(async ({ ctx }) => {
- return courses as JSONCourse[];
+ publicGetAllCourses: publicProcedure.query(async () => {
+ return await courseCache.getCourses(new Date().getFullYear());
}),
publicGetSanitizedCourses: publicProcedure.query(async ({ ctx }) => {
const courses = await ctx.platformPrisma.courses.findMany({
diff --git a/src/server/trpc/router/validator.ts b/src/server/trpc/router/validator.ts
index 3a2196645..77b49326b 100644
--- a/src/server/trpc/router/validator.ts
+++ b/src/server/trpc/router/validator.ts
@@ -3,8 +3,9 @@ import { TRPCError } from '@trpc/server';
import { z } from 'zod';
import { env } from '@/env/server.mjs';
-import courses, { JSONCourse } from '@data/courses.json';
+import { courses as PlatformCourse } from 'prisma/generated/platform';
+import { courseCache } from './courseCache';
import { DegreeNotFound, DegreeValidationError } from './errors';
import { protectedProcedure, router } from '../trpc';
@@ -47,7 +48,13 @@ export const validatorRouter = router({
throw new TRPCError({ code: 'FORBIDDEN' });
}
- const coursesFromApi: JSONCourse[] = courses;
+ let year = new Date().getFullYear(); // If plan has no semesters, default to current year.
+ if (planData.semesters.length > 0) {
+ // If plan has semesters, default to first semester's year.
+ year = Math.min(...planData.semesters.map((sem) => sem.year));
+ }
+
+ const coursesFromAPI: PlatformCourse[] = await courseCache.getCourses(year);
/* sanitizing data from API db.
* TODO: Fix this later somehow
*/
@@ -61,13 +68,16 @@ export const validatorRouter = router({
}
>();
- for (const course of coursesFromApi) {
+ for (const course of coursesFromAPI) {
courseMapWithCodeKey.set(`${course.subject_prefix} ${course.course_number}`, {
prereqs: course.prerequisites,
coreqs: course.corequisites,
co_or_pre_requisites: course.co_or_pre_requisites,
});
- courseMapWithIdKey.set(course.id, `${course.subject_prefix} ${course.course_number}`);
+ courseMapWithIdKey.set(
+ course.internal_course_number,
+ `${course.subject_prefix} ${course.course_number}`,
+ );
}
/* Hash to store pre req data.
@@ -104,7 +114,7 @@ export const validatorRouter = router({
): Array<[Array, number]> => {
const prereqNotMet: Array<[Array, number]> = [];
let count = 0;
- if (requirements.options.length === 0) {
+ if (!requirements || requirements.options.length === 0) {
return [];
}
const temp: [Array, number] = [[], 0];
@@ -152,7 +162,7 @@ export const validatorRouter = router({
): Array<[Array, number]> => {
const coreqNotMet: Array<[Array, number]> = [];
let count = 0;
- if (requirements.options.length === 0) {
+ if (!requirements || requirements.options.length === 0) {
return [];
}
const temp: [Array, number] = [[], 0];
@@ -201,7 +211,7 @@ export const validatorRouter = router({
): Array<[Array, number]> => {
const coreqNotMet: Array<[Array, number]> = [];
let count = 0;
- if (requirements.options.length === 0) {
+ if (!requirements || requirements.options.length === 0) {
return [];
}
const temp: [Array, number] = [[], 0];