From 48e98dd1d6fb007ddbc6a7b59ac52bdc463c8ec7 Mon Sep 17 00:00:00 2001 From: Andrew Wang Date: Thu, 1 Feb 2024 15:51:16 -0800 Subject: [PATCH 01/10] =?UTF-8?q?feat:=20=E2=9C=A8=20add=20restrictions=20?= =?UTF-8?q?to=20prisma=20and=20scraper?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- libs/db/prisma/schema.prisma | 63 ++++++++++++++++++++--------- services/websoc-scraper-v2/index.ts | 8 +++- 2 files changed, 50 insertions(+), 21 deletions(-) diff --git a/libs/db/prisma/schema.prisma b/libs/db/prisma/schema.prisma index 1ab67d64..31faf89f 100644 --- a/libs/db/prisma/schema.prisma +++ b/libs/db/prisma/schema.prisma @@ -5,7 +5,8 @@ generator client { datasource db { provider = "postgresql" - url = env("DATABASE_URL") + // TODO: fix url + url = "postgresql://postgres:postgres@localhost:5432/api?connect_timeout=300" } // The parts above this line are used to configure the Prisma Client directly @@ -44,6 +45,27 @@ enum WebsocSectionType { Tut } +enum RestrictionCode { + A + B + C + D + E + F + G + H + I + J + K + L + M + N + O + R + S + X +} + // Models model CalendarTerm { @@ -213,25 +235,26 @@ model WebsocSectionMeeting { } model WebsocSection { - year String - quarter Quarter - sectionCode Int - timestamp DateTime - geCategories Json - department String - courseNumber String - courseNumeric Int - instructors WebsocSectionInstructor[] - courseTitle String - sectionType WebsocSectionType - units String - meetings WebsocSectionMeeting[] - maxCapacity Int - sectionFull Boolean - waitlistFull Boolean - overEnrolled Boolean - cancelled Boolean - data Json + year String + quarter Quarter + sectionCode Int + timestamp DateTime + geCategories Json + department String + courseNumber String + courseNumeric Int + instructors WebsocSectionInstructor[] + courseTitle String + sectionType WebsocSectionType + units String + meetings WebsocSectionMeeting[] + maxCapacity Int + sectionFull Boolean + waitlistFull Boolean + overEnrolled Boolean + cancelled Boolean + restrictionCodes RestrictionCode[] + data Json @@id([year, quarter, sectionCode, timestamp]) @@unique([year, quarter, sectionCode, timestamp], name: "idx") diff --git a/services/websoc-scraper-v2/index.ts b/services/websoc-scraper-v2/index.ts index d9736a12..6813db6b 100644 --- a/services/websoc-scraper-v2/index.ts +++ b/services/websoc-scraper-v2/index.ts @@ -1,3 +1,4 @@ +import { $Enums } from ".prisma/client"; import { Prisma, PrismaClient } from "@libs/db"; import { getTermDateData } from "@libs/uc-irvine-api/registrar"; import type { @@ -120,6 +121,7 @@ type ProcessedSection = { waitlistFull: boolean; overEnrolled: boolean; cancelled: boolean; + restrictionCodes: $Enums.RestrictionCode[]; data: object; }; }; @@ -134,7 +136,8 @@ const REQUEST_SLEEP_DURATION = 500; * The duration to sleep between scraping runs, or if rate-limited. * Default: 3 minutes in ms */ -const SLEEP_DURATION = 3 * 60 * 1000; +// const SLEEP_DURATION = 3 * 60 * 1000; +const SLEEP_DURATION = 10 * 1000; /** * The duration to sleep when an error is caught. @@ -401,6 +404,9 @@ async function scrape(name: string, term: Term) { parseInt(section.numCurrentlyEnrolled.totalEnrolled, 10) > parseInt(section.maxCapacity, 10), cancelled: section.sectionComment.includes("*** CANCELLED ***"), + restrictionCodes: section.restrictions + ? (section.restrictions.split(/ and | or /) as $Enums.RestrictionCode[]) + : [], data: isolateSection({ school, department, course, section }), }, }; From 64c816c10f5cf50273d6091ac1b9684b6bd68403 Mon Sep 17 00:00:00 2001 From: Andrew Wang Date: Fri, 9 Feb 2024 16:30:26 -0800 Subject: [PATCH 02/10] =?UTF-8?q?feat:=20=E2=9C=A8=20excludeRestrictionCod?= =?UTF-8?q?es=20filter=20for=20REST=20&=20GraphQL?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/routes/v1/graphql/schema/enum.graphql | 23 +++++++++++++++++++ .../routes/v1/graphql/schema/websoc.graphql | 1 + apps/api/src/routes/v1/rest/websoc/lib.ts | 12 ++++++++++ apps/api/src/routes/v1/rest/websoc/schema.ts | 6 +++++ libs/db/prisma/schema.prisma | 3 +-- services/websoc-scraper-v2/index.ts | 3 +-- 6 files changed, 44 insertions(+), 4 deletions(-) diff --git a/apps/api/src/routes/v1/graphql/schema/enum.graphql b/apps/api/src/routes/v1/graphql/schema/enum.graphql index 38820ffc..bf23e2d9 100644 --- a/apps/api/src/routes/v1/graphql/schema/enum.graphql +++ b/apps/api/src/routes/v1/graphql/schema/enum.graphql @@ -43,6 +43,29 @@ enum SectionType { Tap Tut } + +"The set of valid restriction codes." +enum RestrictionCode { + A + B + C + D + E + F + G + H + I + J + K + L + M + N + O + R + S + X +} + "The set of valid options for filtering full courses." enum FullCourses { ANY diff --git a/apps/api/src/routes/v1/graphql/schema/websoc.graphql b/apps/api/src/routes/v1/graphql/schema/websoc.graphql index bc211dad..ada14060 100644 --- a/apps/api/src/routes/v1/graphql/schema/websoc.graphql +++ b/apps/api/src/routes/v1/graphql/schema/websoc.graphql @@ -176,6 +176,7 @@ extend type Query { maxCapacity: String fullCourses: FullCourses cancelledCourses: CancelledCourses + excludeRestrictionCodes: [RestrictionCode!] ): WebsocAPIResponse! "Get data on all available departments." depts: [Department!]! diff --git a/apps/api/src/routes/v1/rest/websoc/lib.ts b/apps/api/src/routes/v1/rest/websoc/lib.ts index 4e37e35c..8894810b 100644 --- a/apps/api/src/routes/v1/rest/websoc/lib.ts +++ b/apps/api/src/routes/v1/rest/websoc/lib.ts @@ -1,3 +1,4 @@ +import { $Enums } from ".prisma/client"; import { Prisma } from "@libs/db"; import { WebsocAPIOptions } from "@libs/uc-irvine-api/websoc"; @@ -204,6 +205,17 @@ export function constructPrismaQuery(parsedQuery: Query): Prisma.WebsocSectionWh ); } + if (parsedQuery.excludeRestrictionCodes) { + console.log(parsedQuery.excludeRestrictionCodes as $Enums.RestrictionCode[]); + AND.push({ + NOT: { + restrictionCodes: { + hasSome: parsedQuery.excludeRestrictionCodes as $Enums.RestrictionCode[], + }, + }, + }); + } + return { AND, diff --git a/apps/api/src/routes/v1/rest/websoc/schema.ts b/apps/api/src/routes/v1/rest/websoc/schema.ts index 10312c38..3bf5e404 100644 --- a/apps/api/src/routes/v1/rest/websoc/schema.ts +++ b/apps/api/src/routes/v1/rest/websoc/schema.ts @@ -51,6 +51,12 @@ export const QuerySchema = z units: z.string().array().or(z.string()).optional().transform(flattenStringsAndSplit), startTime: z.optional(z.literal("").or(z.string().regex(/([1-9]|1[0-2]):[0-5][0-9][ap]m/))), endTime: z.optional(z.literal("").or(z.string().regex(/([1-9]|1[0-2]):[0-5][0-9][ap]m/))), + excludeRestrictionCodes: z + .string() + .array() + .or(z.string()) + .optional() + .transform(flattenStringsAndSplit), }) .refine((x) => x.cache || !x.cacheOnly, { message: "cacheOnly cannot be true if cache is false", diff --git a/libs/db/prisma/schema.prisma b/libs/db/prisma/schema.prisma index 31faf89f..ae283e5c 100644 --- a/libs/db/prisma/schema.prisma +++ b/libs/db/prisma/schema.prisma @@ -5,8 +5,7 @@ generator client { datasource db { provider = "postgresql" - // TODO: fix url - url = "postgresql://postgres:postgres@localhost:5432/api?connect_timeout=300" + url = env("DATABASE_URL") } // The parts above this line are used to configure the Prisma Client directly diff --git a/services/websoc-scraper-v2/index.ts b/services/websoc-scraper-v2/index.ts index 6813db6b..ee743839 100644 --- a/services/websoc-scraper-v2/index.ts +++ b/services/websoc-scraper-v2/index.ts @@ -136,8 +136,7 @@ const REQUEST_SLEEP_DURATION = 500; * The duration to sleep between scraping runs, or if rate-limited. * Default: 3 minutes in ms */ -// const SLEEP_DURATION = 3 * 60 * 1000; -const SLEEP_DURATION = 10 * 1000; +const SLEEP_DURATION = 3 * 60 * 1000; /** * The duration to sleep when an error is caught. From 4ec18b04280b4b467cea2e182bf8bad328d1c80d Mon Sep 17 00:00:00 2001 From: Andrew Wang Date: Mon, 12 Feb 2024 14:28:34 -0800 Subject: [PATCH 03/10] =?UTF-8?q?fix:=20=F0=9F=90=9B=20typescript=20errors?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/api/src/routes/v1/rest/websoc/lib.ts | 3 +-- services/websoc-scraper-v2/index.ts | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/api/src/routes/v1/rest/websoc/lib.ts b/apps/api/src/routes/v1/rest/websoc/lib.ts index 8894810b..de09746c 100644 --- a/apps/api/src/routes/v1/rest/websoc/lib.ts +++ b/apps/api/src/routes/v1/rest/websoc/lib.ts @@ -1,5 +1,4 @@ -import { $Enums } from ".prisma/client"; -import { Prisma } from "@libs/db"; +import { Prisma, $Enums } from "@libs/db"; import { WebsocAPIOptions } from "@libs/uc-irvine-api/websoc"; import type { Query } from "./schema"; diff --git a/services/websoc-scraper-v2/index.ts b/services/websoc-scraper-v2/index.ts index 209133b4..25108e56 100644 --- a/services/websoc-scraper-v2/index.ts +++ b/services/websoc-scraper-v2/index.ts @@ -1,5 +1,5 @@ -import { $Enums } from ".prisma/client"; -import { Prisma, PrismaClient } from "@libs/db"; +import { $Enums } from "@libs/db"; +import { PrismaClient } from "@libs/db"; import { getTermDateData } from "@libs/uc-irvine-api/registrar"; import type { GE, From 692c486d928815204403dc546727e1dfadb56516 Mon Sep 17 00:00:00 2001 From: Andrew Wang Date: Wed, 28 Feb 2024 16:11:37 -0800 Subject: [PATCH 04/10] =?UTF-8?q?fix:=20=F0=9F=90=9B=20eslint=20type=20imp?= =?UTF-8?q?ort?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/api/src/routes/v1/rest/websoc/lib.ts | 2 +- services/websoc-scraper-v2/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/api/src/routes/v1/rest/websoc/lib.ts b/apps/api/src/routes/v1/rest/websoc/lib.ts index 78407ab0..5ca473ab 100644 --- a/apps/api/src/routes/v1/rest/websoc/lib.ts +++ b/apps/api/src/routes/v1/rest/websoc/lib.ts @@ -1,4 +1,4 @@ -import { $Enums } from "@libs/db"; +import type { $Enums } from "@libs/db"; import type { Prisma } from "@libs/db"; import type { WebsocAPIOptions } from "@libs/uc-irvine-lib/websoc"; import { notNull } from "@libs/utils"; diff --git a/services/websoc-scraper-v2/index.ts b/services/websoc-scraper-v2/index.ts index 582a1e20..1709728a 100644 --- a/services/websoc-scraper-v2/index.ts +++ b/services/websoc-scraper-v2/index.ts @@ -1,4 +1,4 @@ -import { $Enums } from "@libs/db"; +import type { $Enums } from "@libs/db"; import { PrismaClient } from "@libs/db"; import { getTermDateData } from "@libs/uc-irvine-lib/registrar"; import type { From fd7dd193ed0e6853723f0ae55bbd20ab0aa23611 Mon Sep 17 00:00:00 2001 From: Andrew Wang Date: Tue, 12 Mar 2024 14:53:07 -0700 Subject: [PATCH 05/10] =?UTF-8?q?feat:=20=E2=9C=A8=20add=20restriction=20c?= =?UTF-8?q?ode=20filtering=20for=20Websoc=20lambda=20client?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/routes/v1/rest/websoc/+endpoint.ts | 29 ++++++++++++++++++- apps/api/src/routes/v1/rest/websoc/lib.ts | 1 - apps/api/src/routes/v1/rest/websoc/schema.ts | 13 ++++++++- 3 files changed, 40 insertions(+), 3 deletions(-) diff --git a/apps/api/src/routes/v1/rest/websoc/+endpoint.ts b/apps/api/src/routes/v1/rest/websoc/+endpoint.ts index f49ce8a1..e8a8f8aa 100644 --- a/apps/api/src/routes/v1/rest/websoc/+endpoint.ts +++ b/apps/api/src/routes/v1/rest/websoc/+endpoint.ts @@ -3,6 +3,7 @@ import { createHandler } from "@libs/lambda"; import type { WebsocAPIResponse } from "@libs/uc-irvine-lib/websoc"; import { notNull } from "@libs/utils"; import { combineAndNormalizeResponses, sortResponse } from "@libs/websoc-utils"; +import type { z } from "zod"; import { ZodError } from "zod"; import { APILambdaClient } from "./APILambdaClient"; @@ -111,7 +112,9 @@ export const GET = createHandler(async (event, context, res) => { queries: normalizeQuery(parsedQuery), }); - return res.createOKResult(websocResults, headers, requestId); + const filteredWebsocResults = filterResults(parsedQuery, websocResults); + + return res.createOKResult(filteredWebsocResults, headers, requestId); } catch (error) { if (error instanceof ZodError) { const messages = error.issues.map((issue) => issue.message); @@ -120,3 +123,27 @@ export const GET = createHandler(async (event, context, res) => { return res.createErrorResult(400, error, requestId); } }, onWarm); + +function filterResults(query: z.infer, websocResults: WebsocAPIResponse) { + const excludeRestrictions = query.excludeRestrictionCodes ?? []; + + if (excludeRestrictions.length) { + return websocResults.schools.map((school) => { + const filteredDepartments = school.departments.map((department) => { + const filteredCourses = department.courses.map((course) => { + const filteredSections = course.sections.filter( + (section) => + !section.restrictions + .split(/ and | or /) + .some((code: string) => excludeRestrictions.includes(code)), + ); + return { ...course, sections: filteredSections }; + }); + return { ...department, courses: filteredCourses }; + }); + return { ...school, departments: filteredDepartments }; + }); + } + + return websocResults; +} diff --git a/apps/api/src/routes/v1/rest/websoc/lib.ts b/apps/api/src/routes/v1/rest/websoc/lib.ts index 5ca473ab..13fd6dd8 100644 --- a/apps/api/src/routes/v1/rest/websoc/lib.ts +++ b/apps/api/src/routes/v1/rest/websoc/lib.ts @@ -202,7 +202,6 @@ export function constructPrismaQuery(parsedQuery: Query): Prisma.WebsocSectionWh } if (parsedQuery.excludeRestrictionCodes) { - console.log(parsedQuery.excludeRestrictionCodes as $Enums.RestrictionCode[]); AND.push({ NOT: { restrictionCodes: { diff --git a/apps/api/src/routes/v1/rest/websoc/schema.ts b/apps/api/src/routes/v1/rest/websoc/schema.ts index 3bf5e404..05cd70d8 100644 --- a/apps/api/src/routes/v1/rest/websoc/schema.ts +++ b/apps/api/src/routes/v1/rest/websoc/schema.ts @@ -1,3 +1,4 @@ +import { $Enums } from "@libs/db"; import { anyArray, cancelledCoursesOptions, @@ -73,7 +74,17 @@ export const QuerySchema = z ) .refine((x) => x.cacheOnly || x.building || !x.room, { message: 'If "building" is provided, "room" must also be provided', - }); + }) + .refine( + (x) => + !x.excludeRestrictionCodes || + x.excludeRestrictionCodes.every((code) => + Object.values($Enums.RestrictionCode).includes(code as $Enums.RestrictionCode), + ), + { + message: `Restriction codes must be in [${Object.values($Enums.RestrictionCode).join(", ")}]`, + }, + ); /** * Type of the parsed query: useful for passing the query as input to other functions. From b947e67b6377dc0e98e0e770f4121a7c986e8d8c Mon Sep 17 00:00:00 2001 From: Andrew Wang Date: Wed, 10 Apr 2024 16:42:56 -0700 Subject: [PATCH 06/10] =?UTF-8?q?fix:=20=F0=9F=90=9B=20reduce=20courses=20?= =?UTF-8?q?without=20sections=20for=20restriction=20code=20filtering?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/api/src/routes/v1/rest/websoc/+endpoint.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/apps/api/src/routes/v1/rest/websoc/+endpoint.ts b/apps/api/src/routes/v1/rest/websoc/+endpoint.ts index e8a8f8aa..50f835f3 100644 --- a/apps/api/src/routes/v1/rest/websoc/+endpoint.ts +++ b/apps/api/src/routes/v1/rest/websoc/+endpoint.ts @@ -1,6 +1,7 @@ import { PrismaClient } from "@libs/db"; import { createHandler } from "@libs/lambda"; import type { WebsocAPIResponse } from "@libs/uc-irvine-lib/websoc"; +import type { WebsocCourse } from "@libs/uc-irvine-lib/websoc"; import { notNull } from "@libs/utils"; import { combineAndNormalizeResponses, sortResponse } from "@libs/websoc-utils"; import type { z } from "zod"; @@ -125,20 +126,27 @@ export const GET = createHandler(async (event, context, res) => { }, onWarm); function filterResults(query: z.infer, websocResults: WebsocAPIResponse) { + if (!query.excludeRestrictionCodes) { + return websocResults; + } + const excludeRestrictions = query.excludeRestrictionCodes ?? []; if (excludeRestrictions.length) { return websocResults.schools.map((school) => { const filteredDepartments = school.departments.map((department) => { - const filteredCourses = department.courses.map((course) => { + const filteredCourses = department.courses.reduce((acc: WebsocCourse[], course) => { const filteredSections = course.sections.filter( (section) => !section.restrictions .split(/ and | or /) .some((code: string) => excludeRestrictions.includes(code)), ); - return { ...course, sections: filteredSections }; - }); + if (filteredSections.length > 0) { + acc.push({ ...course, sections: filteredSections }); + } + return acc; + }, []); return { ...department, courses: filteredCourses }; }); return { ...school, departments: filteredDepartments }; From 5f7cd1b24208f7b350e59b911f7f657ffa1a444d Mon Sep 17 00:00:00 2001 From: Andrew Wang Date: Wed, 24 Apr 2024 11:34:26 -0700 Subject: [PATCH 07/10] =?UTF-8?q?fix:=20=F0=9F=90=9B=20endpoint=20returns?= =?UTF-8?q?=20array=20of=20school=20objects?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/routes/v1/rest/websoc/+endpoint.ts | 44 ++++++++++--------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/apps/api/src/routes/v1/rest/websoc/+endpoint.ts b/apps/api/src/routes/v1/rest/websoc/+endpoint.ts index 50f835f3..11fa7207 100644 --- a/apps/api/src/routes/v1/rest/websoc/+endpoint.ts +++ b/apps/api/src/routes/v1/rest/websoc/+endpoint.ts @@ -1,7 +1,6 @@ import { PrismaClient } from "@libs/db"; import { createHandler } from "@libs/lambda"; -import type { WebsocAPIResponse } from "@libs/uc-irvine-lib/websoc"; -import type { WebsocCourse } from "@libs/uc-irvine-lib/websoc"; +import type { WebsocAPIResponse, WebsocSchool } from "@libs/uc-irvine-lib/websoc"; import { notNull } from "@libs/utils"; import { combineAndNormalizeResponses, sortResponse } from "@libs/websoc-utils"; import type { z } from "zod"; @@ -132,25 +131,28 @@ function filterResults(query: z.infer, websocResults: Websoc const excludeRestrictions = query.excludeRestrictionCodes ?? []; - if (excludeRestrictions.length) { - return websocResults.schools.map((school) => { - const filteredDepartments = school.departments.map((department) => { - const filteredCourses = department.courses.reduce((acc: WebsocCourse[], course) => { - const filteredSections = course.sections.filter( - (section) => - !section.restrictions - .split(/ and | or /) - .some((code: string) => excludeRestrictions.includes(code)), - ); - if (filteredSections.length > 0) { - acc.push({ ...course, sections: filteredSections }); - } - return acc; - }, []); - return { ...department, courses: filteredCourses }; - }); - return { ...school, departments: filteredDepartments }; - }); + if (excludeRestrictions.length > 0) { + websocResults.schools = websocResults.schools + .map((school) => { + school.departments = school.departments + .map((department) => { + department.courses = department.courses + .map((course) => { + course.sections = course.sections.filter( + (section) => + !section.restrictions + .split(/ and | or /) + .some((code: string) => excludeRestrictions.includes(code)), + ); + return course; + }) + .filter((course) => course.sections.length > 0); + return { ...department, courses: department.courses }; + }) + .filter((department) => department.courses.length > 0); + return { ...school, departments: school.departments }; + }) + .filter((school) => school.departments.length > 0) satisfies WebsocSchool[]; } return websocResults; From ac8c6daba4256fbfe105ed4b7526989532189944 Mon Sep 17 00:00:00 2001 From: Andrew Wang Date: Wed, 24 Apr 2024 16:57:17 -0700 Subject: [PATCH 08/10] =?UTF-8?q?fix:=20=F0=9F=90=9B=20remove=20unnecessar?= =?UTF-8?q?y=20copying=20in=20filter=20map?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/api/src/routes/v1/rest/websoc/+endpoint.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/api/src/routes/v1/rest/websoc/+endpoint.ts b/apps/api/src/routes/v1/rest/websoc/+endpoint.ts index 11fa7207..73818da0 100644 --- a/apps/api/src/routes/v1/rest/websoc/+endpoint.ts +++ b/apps/api/src/routes/v1/rest/websoc/+endpoint.ts @@ -147,10 +147,10 @@ function filterResults(query: z.infer, websocResults: Websoc return course; }) .filter((course) => course.sections.length > 0); - return { ...department, courses: department.courses }; + return department; }) .filter((department) => department.courses.length > 0); - return { ...school, departments: school.departments }; + return school; }) .filter((school) => school.departments.length > 0) satisfies WebsocSchool[]; } From 53195b4d332dcad357f62b480d0342071674bc53 Mon Sep 17 00:00:00 2001 From: Andrew Wang Date: Wed, 24 Apr 2024 17:02:28 -0700 Subject: [PATCH 09/10] =?UTF-8?q?docs:=20=F0=9F=93=9A=EF=B8=8F=20add=20fil?= =?UTF-8?q?ter=20function=20return=20type?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/api/src/routes/v1/rest/websoc/+endpoint.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/api/src/routes/v1/rest/websoc/+endpoint.ts b/apps/api/src/routes/v1/rest/websoc/+endpoint.ts index 73818da0..777bb067 100644 --- a/apps/api/src/routes/v1/rest/websoc/+endpoint.ts +++ b/apps/api/src/routes/v1/rest/websoc/+endpoint.ts @@ -3,12 +3,11 @@ import { createHandler } from "@libs/lambda"; import type { WebsocAPIResponse, WebsocSchool } from "@libs/uc-irvine-lib/websoc"; import { notNull } from "@libs/utils"; import { combineAndNormalizeResponses, sortResponse } from "@libs/websoc-utils"; -import type { z } from "zod"; import { ZodError } from "zod"; import { APILambdaClient } from "./APILambdaClient"; import { constructPrismaQuery, normalizeQuery } from "./lib"; -import { QuerySchema } from "./schema"; +import { Query, QuerySchema } from "./schema"; const prisma = new PrismaClient(); @@ -124,7 +123,7 @@ export const GET = createHandler(async (event, context, res) => { } }, onWarm); -function filterResults(query: z.infer, websocResults: WebsocAPIResponse) { +function filterResults(query: Query, websocResults: WebsocAPIResponse): WebsocAPIResponse { if (!query.excludeRestrictionCodes) { return websocResults; } From 195f36c3dbee5a9f2c873da1f1358531f1987864 Mon Sep 17 00:00:00 2001 From: Aponia Date: Tue, 14 May 2024 23:45:09 -0700 Subject: [PATCH 10/10] =?UTF-8?q?style(websoc):=20=F0=9F=8E=A8=20cleanup?= =?UTF-8?q?=20restriction-codes=20implementation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/routes/v1/rest/websoc/+endpoint.ts | 66 +++++++++---------- apps/api/src/routes/v1/rest/websoc/schema.ts | 12 ++-- 2 files changed, 38 insertions(+), 40 deletions(-) diff --git a/apps/api/src/routes/v1/rest/websoc/+endpoint.ts b/apps/api/src/routes/v1/rest/websoc/+endpoint.ts index 777bb067..d7faba2c 100644 --- a/apps/api/src/routes/v1/rest/websoc/+endpoint.ts +++ b/apps/api/src/routes/v1/rest/websoc/+endpoint.ts @@ -1,17 +1,16 @@ import { PrismaClient } from "@libs/db"; import { createHandler } from "@libs/lambda"; -import type { WebsocAPIResponse, WebsocSchool } from "@libs/uc-irvine-lib/websoc"; +import type { WebsocAPIResponse } from "@libs/uc-irvine-lib/websoc"; import { notNull } from "@libs/utils"; import { combineAndNormalizeResponses, sortResponse } from "@libs/websoc-utils"; import { ZodError } from "zod"; import { APILambdaClient } from "./APILambdaClient"; import { constructPrismaQuery, normalizeQuery } from "./lib"; -import { Query, QuerySchema } from "./schema"; +import { QuerySchema } from "./schema"; const prisma = new PrismaClient(); -// let connected = false const lambdaClient = await APILambdaClient.new(); async function onWarm() { @@ -111,7 +110,11 @@ export const GET = createHandler(async (event, context, res) => { queries: normalizeQuery(parsedQuery), }); - const filteredWebsocResults = filterResults(parsedQuery, websocResults); + if (!parsedQuery.excludeRestrictionCodes?.length) { + return res.createOKResult(websocResults, headers, requestId); + } + + const filteredWebsocResults = filterResults(websocResults, parsedQuery.excludeRestrictionCodes); return res.createOKResult(filteredWebsocResults, headers, requestId); } catch (error) { @@ -123,36 +126,27 @@ export const GET = createHandler(async (event, context, res) => { } }, onWarm); -function filterResults(query: Query, websocResults: WebsocAPIResponse): WebsocAPIResponse { - if (!query.excludeRestrictionCodes) { - return websocResults; - } - - const excludeRestrictions = query.excludeRestrictionCodes ?? []; - - if (excludeRestrictions.length > 0) { - websocResults.schools = websocResults.schools - .map((school) => { - school.departments = school.departments - .map((department) => { - department.courses = department.courses - .map((course) => { - course.sections = course.sections.filter( - (section) => - !section.restrictions - .split(/ and | or /) - .some((code: string) => excludeRestrictions.includes(code)), - ); - return course; - }) - .filter((course) => course.sections.length > 0); - return department; - }) - .filter((department) => department.courses.length > 0); - return school; - }) - .filter((school) => school.departments.length > 0) satisfies WebsocSchool[]; - } - - return websocResults; +function filterResults(results: WebsocAPIResponse, restrictionCodes: string[]): WebsocAPIResponse { + results.schools = results.schools + .map((school) => { + school.departments = school.departments + .map((department) => { + department.courses = department.courses + .map((course) => { + course.sections = course.sections.filter( + (section) => + !section.restrictions + .split(/ and | or /) + .some((code: string) => restrictionCodes.includes(code)), + ); + return course; + }) + .filter((course) => course.sections.length > 0); + return department; + }) + .filter((department) => department.courses.length > 0); + return school; + }) + .filter((school) => school.departments.length > 0); + return results; } diff --git a/apps/api/src/routes/v1/rest/websoc/schema.ts b/apps/api/src/routes/v1/rest/websoc/schema.ts index 05cd70d8..f7184b03 100644 --- a/apps/api/src/routes/v1/rest/websoc/schema.ts +++ b/apps/api/src/routes/v1/rest/websoc/schema.ts @@ -76,11 +76,15 @@ export const QuerySchema = z message: 'If "building" is provided, "room" must also be provided', }) .refine( - (x) => - !x.excludeRestrictionCodes || - x.excludeRestrictionCodes.every((code) => + (x) => { + // If not excluding restriction codes, then no more validation is needed. + if (x.excludeRestrictionCodes == null) return true; + + // Ensure that all provided restriction codes are valid. + return x.excludeRestrictionCodes.every((code) => Object.values($Enums.RestrictionCode).includes(code as $Enums.RestrictionCode), - ), + ); + }, { message: `Restriction codes must be in [${Object.values($Enums.RestrictionCode).join(", ")}]`, },