Skip to content
This repository has been archived by the owner on Oct 18, 2024. It is now read-only.

feat: ✨ add enrollment history endpoint & scraper support #108

Merged
merged 14 commits into from
Nov 16, 2023
Merged
1 change: 1 addition & 0 deletions apps/api/src/routes/v1/graphql/resolvers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const resolvers: ApolloServerOptions<BaseContext>["resolvers"] = {
course: proxyRestApi("/v1/rest/courses", { pathArg: "courseId" }),
courses: proxyRestApi("/v1/rest/courses", { argsTransform: geTransform }),
allCourses: proxyRestApi("/v1/rest/courses/all"),
enrollmentHistory: proxyRestApi("/v1/rest/enrollmentHistory"),
rawGrades: proxyRestApi("/v1/rest/grades/raw"),
aggregateGrades: proxyRestApi("/v1/rest/grades/aggregate"),
gradesOptions: proxyRestApi("/v1/rest/grades/options"),
Expand Down
33 changes: 33 additions & 0 deletions apps/api/src/routes/v1/graphql/schema/enrollmentHistory.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
type EnrollmentHistory {
year: String!
quarter: Quarter!
sectionCode: String!
department: String!
courseNumber: String!
sectionType: SectionType!
sectionNum: String!
units: String
instructors: [String!]!
meetings: [String!]!
finalExam: String
dates: [String!]!
maxCapacityHistory: [String!]!
totalEnrolledHistory: [String!]!
waitlistHistory: [String!]!
waitlistCapHistory: [String!]!
requestedHistory: [String!]!
newOnlyReservedHistory: [String!]!
statusHistory: [String!]!
}

extend type Query {
enrollmentHistory(
year: String
quarter: Quarter
instructor: String
department: String
courseNumber: String
sectionCode: String
sectionType: SectionType
): [EnrollmentHistory!]!
}
33 changes: 33 additions & 0 deletions apps/api/src/routes/v1/rest/enrollmentHistory/+endpoint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { PrismaClient } from "@libs/db";
import { createHandler } from "@libs/lambda";
import type { EnrollmentHistory } from "@peterportal-api/types";

import { QuerySchema } from "./schema";

const prisma = new PrismaClient();

export const GET = createHandler(async (event, context, res) => {
const { headers, queryStringParameters: query } = event;
const { awsRequestId: requestId } = context;

const maybeParsed = QuerySchema.safeParse(query);
if (!maybeParsed.success) {
return res.createErrorResult(400, maybeParsed.error, requestId);
}
const {
data: { instructor, ...data },
} = maybeParsed;

return res.createOKResult<EnrollmentHistory[]>(
(
await prisma.websocEnrollmentHistory.findMany({
where: { ...data, instructors: { array_contains: instructor } },
})
).map((x) => {
const { timestamp: _, ...obj } = x;
return obj as unknown as EnrollmentHistory;
}),
headers,
requestId,
);
});
23 changes: 23 additions & 0 deletions apps/api/src/routes/v1/rest/enrollmentHistory/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { quarters, sectionTypes } from "@peterportal-api/types";
import { z } from "zod";

export const QuerySchema = z.object({
year: z
.string()
.regex(/^\d{4}$/, { message: "Invalid year provided" })
.optional(),
quarter: z.enum(quarters, { invalid_type_error: "Invalid quarter provided" }).optional(),
instructor: z.string().optional(),
department: z.string().optional(),
courseNumber: z.string().optional(),
sectionCode: z
.string()
.regex(/^\d{5}$/, { message: "Invalid sectionCode provided" })
.transform((x) => Number.parseInt(x, 10))
.optional(),
sectionType: z
.enum(sectionTypes, { invalid_type_error: "Invalid sectionType provided" })
.optional(),
});

export type Query = z.infer<typeof QuerySchema>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
---
pagination_prev: null
pagination_next: null
---

import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";

# Enrollment History

The enrollment history endpoint allows users to view the changes in enrollment data (total enrolled, max capacity, waitlist, etc.) for a specific course's sections.

## Query parameters for all endpoints

#### `year` string

The year to include.

#### `quarter` Fall | Winter | Spring | Summer1 | Summer10wk | Summer2

The quarter to include. Case-sensitive.

#### `instructor` string

The shortened name of the instructor to include. (Ex.: `SHINDLER, M.`)

#### `courseNumber` string

The course number to include. (Ex.: 161)

#### `sectionCode` string

The five-digit section code to include.

#### `sectionType` | Act | Col | Dis | Fld | Lab | Lec | Qiz | Res | Sem | Stu | Tap | Tut

The section type code. Case-sensitive.

### Code sample

<Tabs>
<TabItem value="bash" label="cURL">

```bash
curl "https://api-next.peterportal.org/v1/rest/enrollmentHistory?year=2022&quarter=Fall&department=I%26C%20SCI&courseNumber=46"
```

</TabItem>
</Tabs>

### Response

<Tabs>
<TabItem value="json" label="Example response">

```json
[
{
"year": "2022",
"quarter": "Fall",
"sectionCode": 35730,
"department": "I&C SCI",
"courseNumber": "46",
"sectionType": "Lec",
"sectionNum": "A",
"units": "4",
"instructors": ["SHINDLER, M.", "GARZA RODRIGUE, A.", "GILA, O."],
"meetings": [{ "days": "MWF", "time": "8:00- 8:50", "bldg": ["EH 1200"] }],
"finalExam": "Mon, Dec 5, 8:00-10:00am",
"dates": ["2022-05-17", "2022-05-18", "..."],
"maxCapacityHistory": ["220", "220", "220", "..."],
"totalEnrolledHistory": ["5", "5", "7", "..."],
"waitlistHistory": ["n/a", "n/a", "n/a", "..."],
"waitlistCapHistory": ["0", "0", "0", "..."],
"requestedHistory": ["7", "8", "11", "..."],
"newOnlyReservedHistory": ["0", "0", "0", "..."],
"statusHistory": ["OPEN", "OPEN", "OPEN", "..."]
},
{
"year": "2022",
"quarter": "Fall",
"sectionCode": 35740,
"department": "I&C SCI",
"courseNumber": "46",
"sectionType": "Lec",
"sectionNum": "B",
"units": "4",
"instructors": ["SHINDLER, M.", "DICKERSON, M."],
"meetings": [{ "days": "MWF", "time": "10:00-10:50", "bldg": ["SSLH 100"] }],
"finalExam": "Mon, Dec 5, 10:30-12:30pm",
"dates": ["2022-05-17", "2022-05-18", "..."],
"maxCapacityHistory": ["220", "220", "220", "..."],
"totalEnrolledHistory": ["38", "44", "58", "..."],
"waitlistHistory": ["n/a", "n/a", "n/a", "..."],
"waitlistCapHistory": ["0", "0", "0", "..."],
"requestedHistory": ["41", "49", "66", "..."],
"newOnlyReservedHistory": ["0", "0", "0", "..."],
"statusHistory": ["OPEN", "OPEN", "OPEN", "..."]
}
]
```

</TabItem>
<TabItem value="ts" label="Payload schema">

```typescript
// https://github.com/icssc/peterportal-api-next/blob/main/packages/peterportal-api-next-types/types/calendar.ts
type EnrollmentHistory = {
year: string;
quarter: Quarter;
sectionCode: string;
department: string;
courseNumber: string;
sectionType: string;
sectionNum: string;
units: string;
instructors: string[];
meetings: string[];
finalExam: string;
dates: string[];
maxCapacityHistory: string[];
totalEnrolledHistory: string[];
waitlistHistory: string[];
waitlistCapHistory: string[];
requestedHistory: string[];
newOnlyReservedHistory: string[];
statusHistory: string[];
};
```

</TabItem>
</Tabs>
1 change: 1 addition & 0 deletions apps/docs/sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const sidebars = {
items: [
"developers-guide/rest-api/reference/calendar",
"developers-guide/rest-api/reference/courses",
"developers-guide/rest-api/reference/enrollment-history",
"developers-guide/rest-api/reference/grades",
"developers-guide/rest-api/reference/instructors",
"developers-guide/rest-api/reference/larc",
Expand Down
26 changes: 26 additions & 0 deletions libs/db/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,32 @@ model GradesSection {
@@unique([year, quarter, sectionCode], name: "idx")
}

model WebsocEnrollmentHistory {
year String
quarter Quarter
sectionCode Int
timestamp DateTime
department String
courseNumber String
sectionType WebsocSectionType
sectionNum String
units String
instructors Json
meetings Json
finalExam String
dates Json
maxCapacityHistory Json
totalEnrolledHistory Json
waitlistHistory Json
waitlistCapHistory Json
requestedHistory Json
newOnlyReservedHistory Json
statusHistory Json

@@id([year, quarter, sectionCode, timestamp])
@@unique([year, quarter, sectionCode, timestamp], name: "idx")
}

model WebsocSectionInstructor {
id Int @id @default(autoincrement())
year String
Expand Down
1 change: 1 addition & 0 deletions packages/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from "./types/calendar";
export * from "./types/constants";
export * from "./types/courses";
export * from "./types/enrollmentHistory";
export * from "./types/grades";
export * from "./types/instructor";
export * from "./types/larc";
Expand Down
23 changes: 23 additions & 0 deletions packages/types/types/enrollmentHistory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Quarter, SectionType } from "./constants";

export type EnrollmentHistory = {
year: string;
quarter: Quarter;
sectionCode: string;
department: string;
courseNumber: string;
sectionType: SectionType;
sectionNum: string;
units: string;
instructors: string[];
meetings: string[];
finalExam: string;
dates: string[];
maxCapacityHistory: string[];
totalEnrolledHistory: string[];
waitlistHistory: string[];
waitlistCapHistory: string[];
requestedHistory: string[];
newOnlyReservedHistory: string[];
statusHistory: string[];
};
Loading