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

Commit

Permalink
feat: ✨ add calendar scraper and dump endpoint (#132)
Browse files Browse the repository at this point in the history
  • Loading branch information
ecxyzzy authored Feb 28, 2024
1 parent f8539de commit 011e6d3
Show file tree
Hide file tree
Showing 15 changed files with 353 additions and 226 deletions.
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 @@ -4,6 +4,7 @@ import { geTransform, proxyRestApi } from "./lib";

export const resolvers: ApolloServerOptions<BaseContext>["resolvers"] = {
Query: {
allTermDates: proxyRestApi("/v1/rest/calendar"),
calendar: proxyRestApi("/v1/rest/calendar"),
course: proxyRestApi("/v1/rest/courses", { pathArg: "courseId" }),
courses: proxyRestApi("/v1/rest/courses", { argsTransform: geTransform }),
Expand Down
22 changes: 15 additions & 7 deletions apps/api/src/routes/v1/graphql/schema/calendar.graphql
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
"An object that includes important dates for a specified quarter."
type QuarterDates {
"When instruction begins for the given quarter."
type TermDates {
"The year of the given term."
year: String!
"The quarter of the given term."
quarter: String!
"When instruction begins for the given term."
instructionStart: Date!
"When instruction ends for the given quarter."
"When instruction ends for the given term."
instructionEnd: Date!
"When finals begin for the given quarter."
"When finals begin for the given term."
finalsStart: Date!
"When finals end for the given quarter."
"When finals end for the given term."
finalsEnd: Date!
"When the Schedule of Classes becomes available for the given term."
socAvailable: Date!
}

extend type Query {
"Get important dates for a quarter."
calendar(year: String!, quarter: Quarter!): QuarterDates!
"Get all available terms and their important dates."
allTermDates: [TermDates!]!
"Get important dates for a term."
calendar(year: String!, quarter: Quarter!): TermDates!
}
73 changes: 24 additions & 49 deletions apps/api/src/routes/v1/rest/calendar/+endpoint.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { PrismaClient } from "@libs/db";
import { createHandler } from "@libs/lambda";
import { getTermDateData } from "@libs/uc-irvine-lib/registrar";
import type { Quarter, QuarterDates } from "@peterportal-api/types";
import { ZodError } from "zod";
import type { QuarterDates } from "@peterportal-api/types";

import { QuerySchema } from "./schema";

Expand All @@ -14,56 +12,33 @@ async function onWarm() {

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

try {
const where = QuerySchema.parse(query);
const maybeParsed = QuerySchema.safeParse(query);

const result = await prisma.calendarTerm.findFirst({
where,
select: {
instructionStart: true,
instructionEnd: true,
finalsStart: true,
finalsEnd: true,
},
});

if (result) {
return res.createOKResult<QuarterDates>(result, headers, requestId);
}

const termDateData = await getTermDateData(
where.quarter === "Fall" ? where.year : (parseInt(where.year) - 1).toString(10),
);

await prisma.calendarTerm.createMany({
data: Object.entries(termDateData).map(([term, data]) => ({
year: term.split(" ")[0],
quarter: term.split(" ")[1] as Quarter,
...data,
})),
});

if (!Object.keys(termDateData).length) {
return res.createErrorResult(
400,
`The requested term, ${where.year} ${where.quarter}, is currently unavailable.`,
requestId,
);
}

return res.createOKResult(
termDateData[[where.year, where.quarter].join(" ")],
headers,
if (!maybeParsed.success)
return res.createErrorResult(
400,
maybeParsed.error.issues.map((issue) => issue.message).join("; "),
requestId,
);
} catch (error) {
if (error instanceof ZodError) {
const messages = error.issues.map((issue) => issue.message);
return res.createErrorResult(400, messages.join("; "), requestId);
}
return res.createErrorResult(400, error, requestId);

const { data: where } = maybeParsed;

if ("year" in where) {
const result = await prisma.calendarTerm.findFirst({ where });
return result
? res.createOKResult<QuarterDates>(result, headers, requestId)
: res.createErrorResult(
400,
`The requested term, ${where.year} ${where.quarter}, is currently unavailable.`,
requestId,
);
}
return res.createOKResult(
await prisma.calendarTerm.findMany({ orderBy: { instructionStart: "asc" } }),
headers,
requestId,
);
}, onWarm);
20 changes: 11 additions & 9 deletions apps/api/src/routes/v1/rest/calendar/schema.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { quarters } from "@peterportal-api/types";
import { z } from "zod";

export const QuerySchema = z.object({
year: z
.string({ required_error: 'Parameter "year" not provided' })
.length(4, { message: "Invalid year provided" }),
export const QuerySchema = z
.object({
year: z
.string({ required_error: 'Parameter "year" not provided' })
.length(4, { message: "Invalid year provided" }),

quarter: z.enum(quarters, {
required_error: 'Parameter "quarter" not provided',
invalid_type_error: "Invalid quarter provided",
}),
});
quarter: z.enum(quarters, {
required_error: 'Parameter "quarter" not provided',
invalid_type_error: "Invalid quarter provided",
}),
})
.or(z.object({}));

export type Query = z.infer<typeof QuerySchema>;
9 changes: 5 additions & 4 deletions libs/db/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,11 @@ enum WebsocSectionType {
model CalendarTerm {
year String
quarter Quarter
instructionStart DateTime
instructionEnd DateTime
finalsStart DateTime
finalsEnd DateTime
instructionStart DateTime @db.Date
instructionEnd DateTime @db.Date
finalsStart DateTime @db.Date
finalsEnd DateTime @db.Date
socAvailable DateTime @default("1970-01-01T00:00:00Z") @db.Date
@@id([year, quarter])
@@unique([year, quarter], name: "idx")
Expand Down
Loading

0 comments on commit 011e6d3

Please sign in to comment.