From 963a602b5f095ae004e87592cba5416934a28f02 Mon Sep 17 00:00:00 2001 From: Kevin Wu Date: Wed, 3 Apr 2024 16:15:53 -0700 Subject: [PATCH 1/4] feat: update api endpoint --- lib/utils/query-db.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/utils/query-db.ts b/lib/utils/query-db.ts index d2c4b0b..0e58090 100644 --- a/lib/utils/query-db.ts +++ b/lib/utils/query-db.ts @@ -20,7 +20,7 @@ export async function queryDatabase( const universityParam = encodeURIComponent(university); const geParam = encodeURIComponent(ge); - const url = `https://ge-z.info:5000/api/cvc-courses?uni=${universityParam}&ge=${geParam}`; + const url = `https://ge-z.info/api/cvc-courses?institution=${universityParam}&ge=${geParam}`; try { const response = await fetch(url); @@ -30,9 +30,9 @@ export async function queryDatabase( const data = await response.json(); - cache[cacheKey] = [new Date(), data.courses]; + cache[cacheKey] = [new Date(), data.data]; - return data.courses; + return data.data; } catch (error) { console.error("Error:", error); throw error; From 0c97afc0a3cb531efaf711082a8f04ae760a0c7e Mon Sep 17 00:00:00 2001 From: Kevin Wu Date: Wed, 3 Apr 2024 16:39:03 -0700 Subject: [PATCH 2/4] refactor: CollegeObject -> CourseObject --- components/search/Blurb.tsx | 8 ++++---- components/search/Search.tsx | 20 ++++++++++--------- components/search/filter/FilterComponents.tsx | 6 +++--- components/search/filter/Filters.tsx | 4 ++-- lib/utils/query-db.ts | 6 +++--- 5 files changed, 23 insertions(+), 21 deletions(-) diff --git a/components/search/Blurb.tsx b/components/search/Blurb.tsx index 353fca1..92ecd99 100644 --- a/components/search/Blurb.tsx +++ b/components/search/Blurb.tsx @@ -1,12 +1,12 @@ import React from "react"; -import { CollegeObject, FilterValues } from "./Search"; +import { CourseObject, FilterValues } from "./Search"; interface BlurbProps { filterData: ( - data: CollegeObject[], + data: CourseObject[], filterValues: FilterValues, - ) => CollegeObject[]; - data: CollegeObject[] | undefined; + ) => CourseObject[]; + data: CourseObject[] | undefined; filterValues: FilterValues; } diff --git a/components/search/Search.tsx b/components/search/Search.tsx index a0aef05..37cedd9 100644 --- a/components/search/Search.tsx +++ b/components/search/Search.tsx @@ -19,28 +19,30 @@ import Link from "next/link"; import { SearchSelect } from "./SearchSelect"; import { getDismissedRecently, getNumSearches } from "@/lib/utils/search"; -export interface CollegeObject { +export interface CourseObject { sendingInstitution: string; courseCode: string; courseName: string; cvcId: string; + assistPath: string; niceToHaves: string[]; units: number; - term: string; - startMonth: number; - startDay: number; - endMonth: number; - endDay: number; tuition: number; + startDate: number; + endDate: number; async: boolean; hasOpenSeats: boolean; hasPrereqs: boolean; instantEnrollment: boolean; - fulfillsGEs: string[]; + fulfillsGEs: FullFillsGE[]; articulatesTo: string[]; - assistPath: string; } +type FullFillsGE = { + category: string; + count: number; +}; + export type FilterValues = { format: boolean[]; enrollment: boolean[]; @@ -112,7 +114,7 @@ const Search = () => { const [sort, setSort] = useState("Default Sort"); - const [courses, setCourses] = useState(); + const [courses, setCourses] = useState(); const [filterValues, setFilterValues] = useState({ format: format, diff --git a/components/search/filter/FilterComponents.tsx b/components/search/filter/FilterComponents.tsx index 319bedb..49e6fae 100644 --- a/components/search/filter/FilterComponents.tsx +++ b/components/search/filter/FilterComponents.tsx @@ -2,7 +2,7 @@ import React, { ChangeEvent, Dispatch, SetStateAction, useState } from "react"; import { FaCheck } from "react-icons/fa"; -import { CollegeObject } from "../Search"; +import { CourseObject } from "../Search"; import { format } from "date-fns"; import { Calendar } from "@/components/ui/calendar"; @@ -185,7 +185,7 @@ export const CalendarFilter = (props: CalendarFilterProps) => { interface InstitutionDropdownProps { defaultValue: string; - courses: CollegeObject[] | undefined; + courses: CourseObject[] | undefined; onChange: Dispatch>; } @@ -203,7 +203,7 @@ export const InstitutionDropdown = (props: InstitutionDropdownProps) => { defaultValue == "Any Institution" ? [] : [defaultValue]; const sendingInstitutions = courses?.map( - (course: CollegeObject) => course.sendingInstitution, + (course: CourseObject) => course.sendingInstitution, ); if (sendingInstitutions) { diff --git a/components/search/filter/Filters.tsx b/components/search/filter/Filters.tsx index 87ceb00..75a3924 100644 --- a/components/search/filter/Filters.tsx +++ b/components/search/filter/Filters.tsx @@ -6,7 +6,7 @@ import { UnitsFilter, } from "./FilterComponents"; import { FaCircleXmark } from "react-icons/fa6"; -import { CollegeObject, FilterValues } from "../Search"; +import { CourseObject, FilterValues } from "../Search"; interface SearchFilterProps { handleClick: () => void; @@ -19,7 +19,7 @@ interface SearchFilterProps { setMin: Dispatch>; setMax: Dispatch>; filterValues: FilterValues; - courses: CollegeObject[] | undefined; + courses: CourseObject[] | undefined; } export const SearchFilters = (props: SearchFilterProps) => { diff --git a/lib/utils/query-db.ts b/lib/utils/query-db.ts index 0e58090..c8cef58 100644 --- a/lib/utils/query-db.ts +++ b/lib/utils/query-db.ts @@ -1,11 +1,11 @@ -import { CollegeObject } from "../../components/search/Search"; +import { CourseObject } from "../../components/search/Search"; -const cache: Record = {}; +const cache: Record = {}; export async function queryDatabase( university: string, ge: string, -): Promise { +): Promise { const cacheKey = university + ge; if (cache[cacheKey] && cache[cacheKey][0]) { From 12aef46c6989c7cf2713cfd9744b238cc85178e1 Mon Sep 17 00:00:00 2001 From: Kevin Wu Date: Wed, 3 Apr 2024 16:39:41 -0700 Subject: [PATCH 3/4] feat: update functions, filters to new schema --- components/search/SearchResults.tsx | 20 +++++++++++++++----- lib/utils/filter.ts | 29 ++++++++++------------------- 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/components/search/SearchResults.tsx b/components/search/SearchResults.tsx index 61437d7..21b9682 100644 --- a/components/search/SearchResults.tsx +++ b/components/search/SearchResults.tsx @@ -1,10 +1,15 @@ // import LazyLoad from "react-lazy-load"; import { FaUpRightFromSquare } from "react-icons/fa6"; -import { CollegeObject } from "./Search"; +import { CourseObject } from "./Search"; import Tags from "./Tags"; +import { format } from "date-fns"; + +const formatTime = (date: number) => { + return format(new Date(date), "MMM d"); +}; interface SearchResultsProps { - results: CollegeObject[]; + results: CourseObject[]; university: string; ge: string; } @@ -16,7 +21,10 @@ const SearchResults = (props: SearchResultsProps) => { <>
{results.length > 0 ? ( - results.map((result: CollegeObject) => { + results.map((result: CourseObject) => { + const startTime = formatTime(result.startDate); + const endTime = formatTime(result.endDate); + return ( // { Term
- {result.term} + {startTime + " - " + endTime}
@@ -79,7 +87,9 @@ const SearchResults = (props: SearchResultsProps) => { GEs
- {result.fulfillsGEs.join(", ")} + {result.fulfillsGEs + .map((obj) => obj.category) + .join(", ")}
diff --git a/lib/utils/filter.ts b/lib/utils/filter.ts index 5f6ce7c..0a24fce 100644 --- a/lib/utils/filter.ts +++ b/lib/utils/filter.ts @@ -1,23 +1,21 @@ -import { CollegeObject, FilterValues } from "../../components/search/Search"; +import { CourseObject, FilterValues } from "../../components/search/Search"; + +export const startsAfter = (start: Date, result: CourseObject) => { + const courseDate = new Date(result.startDate); -export const startsAfter = (start: Date, result: CollegeObject) => { - const month = result.startMonth.toString().padStart(2, "0"); - const day = result.startDay.toString().padStart(2, "0"); - const courseDate = new Date(`2024-${month}-${day}`); courseDate.setHours(0, 0, 0, 0); courseDate.setDate(courseDate.getDate() + 1); return courseDate > start; }; -export const endsBefore = (end: Date | undefined, result: CollegeObject) => { +export const endsBefore = (end: Date | undefined, result: CourseObject) => { if (end == undefined) { return true; } - const month = result.endMonth.toString().padStart(2, "0"); - const day = result.endDay.toString().padStart(2, "0"); - const courseDate = new Date(`2024-${month}-${day}`); + const courseDate = new Date(result.endDate); + courseDate.setHours(0, 0, 0, 0); courseDate.setDate(courseDate.getDate() + 1); @@ -25,7 +23,7 @@ export const endsBefore = (end: Date | undefined, result: CollegeObject) => { }; export function filterData( - data: CollegeObject[] | undefined, + data: CourseObject[] | undefined, filterValues: FilterValues, ) { if (!data) { @@ -78,15 +76,8 @@ export function filterData( }) : filterValues.sort == "Shortest Term" ? filteredResults.sort((courseA, courseB) => { - const termLengthA = - ((courseA.endMonth - courseA.startMonth + 12) % 12) * - 30 + - (courseA.endDay - courseA.startDay); - - const termLengthB = - ((courseB.endMonth - courseB.startMonth + 12) % 12) * - 30 + - (courseB.endDay - courseB.startDay); + const termLengthA = courseA.endDate - courseA.startDate; + const termLengthB = courseB.endDate - courseB.startDate; return termLengthA - termLengthB; }) From fd2bf8114a1cce4335ffcdc1390798f963229f57 Mon Sep 17 00:00:00 2001 From: Kevin Wu Date: Wed, 3 Apr 2024 18:39:13 -0700 Subject: [PATCH 4/4] test: update tests --- __tests__/search-filters.test.ts | 98 ++++++++++++++++---------------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/__tests__/search-filters.test.ts b/__tests__/search-filters.test.ts index 828713a..d771b3b 100644 --- a/__tests__/search-filters.test.ts +++ b/__tests__/search-filters.test.ts @@ -1,11 +1,16 @@ -import { FilterValues } from "@/components/search/Search"; +import { CourseObject, FilterValues } from "@/components/search/Search"; import { endsBefore, filterData, startsAfter } from "@/lib/utils/filter"; import "@testing-library/jest-dom"; -const data = { - institution: "University of California, Irvine", - geCategory: "II", - courses: [ +interface CourseData { + status: number; + data: CourseObject[]; + lastUpdated: number; // unix +} + +const data: CourseData = { + status: 200, + data: [ { sendingInstitution: "placeholder sending institution 1", courseCode: "placeholder course code 1", @@ -13,11 +18,8 @@ const data = { cvcId: "123456", niceToHaves: ["Zero Textbook Cost"], units: 4, - term: "January 1 - May 31", - startMonth: 1, - startDay: 1, - endMonth: 5, - endDay: 31, + startDate: 1704096000000, + endDate: 1717138800000, tuition: 138, async: true, hasOpenSeats: true, @@ -25,7 +27,7 @@ const data = { instantEnrollment: true, assistPath: "placeholder path 1", articulatesTo: ["placeholder course 1"], - fulfillsGEs: ["II"], + fulfillsGEs: [{ category: "II", count: 1 }], }, { sendingInstitution: "placeholder sending institution 2", @@ -34,11 +36,8 @@ const data = { cvcId: "1234567", niceToHaves: ["Zero Textbook Cost"], units: 16, - term: "January 1 - June 1", - startMonth: 1, - startDay: 1, - endMonth: 6, - endDay: 1, + startDate: 1704096000000, + endDate: 1717225200000, tuition: 100, async: false, hasOpenSeats: false, @@ -46,7 +45,7 @@ const data = { instantEnrollment: false, assistPath: "placeholder path 2", articulatesTo: ["placeholder course 2"], - fulfillsGEs: ["II"], + fulfillsGEs: [{ category: "II", count: 1 }], }, { sendingInstitution: "placeholder sending institution 3", @@ -55,11 +54,8 @@ const data = { cvcId: "1234567", niceToHaves: ["Zero Textbook Cost"], units: 16, - term: "January 1 - June 1", - startMonth: 12, - startDay: 30, - endMonth: 1, - endDay: 1, + startDate: 1704009600000, + endDate: 1704096000000, tuition: 100, async: false, hasOpenSeats: false, @@ -67,9 +63,10 @@ const data = { instantEnrollment: false, assistPath: "placeholder path 3", articulatesTo: ["placeholder course 3"], - fulfillsGEs: ["II"], + fulfillsGEs: [{ category: "II", count: 1 }], }, ], + lastUpdated: 100, }; const defaultFilterValues: FilterValues = { @@ -90,20 +87,20 @@ describe("Search Filters", () => { }); test("default filter values do not throw error", async () => { - expect(() => filterData(data.courses, defaultFilterValues)).not.toThrow( + expect(() => filterData(data.data, defaultFilterValues)).not.toThrow( Error, ); }); test("default filter values return all", async () => { - const result = filterData(data.courses, { + const result = filterData(data.data, { ...defaultFilterValues, }); - expect(result).toEqual(data.courses); + expect(result).toEqual(data.data); }); test("no formats filters correctly", async () => { - const result = filterData(data.courses, { + const result = filterData(data.data, { ...defaultFilterValues, format: [false, false], }); @@ -111,15 +108,15 @@ describe("Search Filters", () => { }); test("both formats filters correctly", async () => { - const result = filterData(data.courses, { + const result = filterData(data.data, { ...defaultFilterValues, format: [true, true], }); - expect(result).toEqual(data.courses); + expect(result).toEqual(data.data); }); test("only async format filters correctly", async () => { - const result = filterData(data.courses, { + const result = filterData(data.data, { ...defaultFilterValues, format: [true, false], }); @@ -127,7 +124,7 @@ describe("Search Filters", () => { }); test("only sync format filters correctly", async () => { - const result = filterData(data.courses, { + const result = filterData(data.data, { ...defaultFilterValues, format: [false, true], }); @@ -136,7 +133,7 @@ describe("Search Filters", () => { }); test("only instant enrollment filters correctly", async () => { - const result = filterData(data.courses, { + const result = filterData(data.data, { ...defaultFilterValues, enrollment: [true], }); @@ -145,7 +142,7 @@ describe("Search Filters", () => { }); test("only available seats filters correctly", async () => { - const result = filterData(data.courses, { + const result = filterData(data.data, { ...defaultFilterValues, available: [true], }); @@ -153,15 +150,15 @@ describe("Search Filters", () => { }); test("any institution filters correctly", async () => { - const result = filterData(data.courses, { + const result = filterData(data.data, { ...defaultFilterValues, institution: "Any Institution", }); - expect(result).toEqual(data.courses); + expect(result).toEqual(data.data); }); test("specific institution filters correctly", async () => { - const result = filterData(data.courses, { + const result = filterData(data.data, { ...defaultFilterValues, institution: "placeholder sending institution 1", }); @@ -171,7 +168,7 @@ describe("Search Filters", () => { }); test("min units filters correctly", async () => { - const result = filterData(data.courses, { + const result = filterData(data.data, { ...defaultFilterValues, min: 5, }); @@ -179,7 +176,7 @@ describe("Search Filters", () => { }); test("max units filters correctly", async () => { - const result = filterData(data.courses, { + const result = filterData(data.data, { ...defaultFilterValues, max: 5, }); @@ -189,32 +186,32 @@ describe("Search Filters", () => { describe("Filter Utils' Time Utilities", () => { test("startsAfter defined returns true", async () => { - const result = startsAfter(new Date("2023-12-25"), data.courses[0]); + const result = startsAfter(new Date("2023-12-25"), data.data[0]); expect(result).toBe(true); }); test("startsAfter defined returns false", async () => { - const result = startsAfter(new Date("2024-12-25"), data.courses[0]); + const result = startsAfter(new Date("2024-12-25"), data.data[0]); expect(result).toBe(false); }); test("endsBefore undefined", async () => { - const result = endsBefore(undefined, data.courses[0]); + const result = endsBefore(undefined, data.data[0]); expect(result).toBe(true); }); test("endsBefore defined returns true", async () => { - const result = endsBefore(new Date("2024-06-14"), data.courses[0]); + const result = endsBefore(new Date("2024-06-14"), data.data[0]); expect(result).toBe(true); }); test("endsBefore defined returns false", async () => { - const result = endsBefore(new Date("2024-05-14"), data.courses[0]); + const result = endsBefore(new Date("2024-05-14"), data.data[0]); expect(result).toBe(false); }); test("tuition sorts correctly", async () => { - const result = filterData(data.courses, { + const result = filterData(data.data, { ...defaultFilterValues, sort: "Tuition", }); @@ -226,7 +223,7 @@ describe("Filter Utils' Time Utilities", () => { describe("Search Sorting", () => { test("default sorts correctly", async () => { - const result = filterData(data.courses, { + const result = filterData(data.data, { ...defaultFilterValues, sort: "Alphabetical", }); @@ -236,7 +233,7 @@ describe("Search Sorting", () => { }); test("alphabetical sorts correctly", async () => { - const result = filterData(data.courses, { + const result = filterData(data.data, { ...defaultFilterValues, sort: "Alphabetical", }); @@ -246,7 +243,7 @@ describe("Search Sorting", () => { }); test("tuition sorts correctly", async () => { - const result = filterData(data.courses, { + const result = filterData(data.data, { ...defaultFilterValues, sort: "Tuition", }); @@ -256,7 +253,7 @@ describe("Search Sorting", () => { }); test("shortest term sorts correctly", async () => { - const result = filterData(data.courses.slice(0, 2), { + const result = filterData(data.data.slice(0, 2), { ...defaultFilterValues, sort: "Shortest Term", }); @@ -266,10 +263,13 @@ describe("Search Sorting", () => { }); test("shortest term sorts correctly on december courses", async () => { - const result = filterData(data.courses, { + const result = filterData(data.data, { ...defaultFilterValues, sort: "Shortest Term", }); + + console.log(result); + expect(result[0].sendingInstitution).toEqual( "placeholder sending institution 3", );