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

feat: ✨ add calendar scraper and dump endpoint #132

Merged
merged 9 commits into from
Feb 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading