Skip to content

Commit

Permalink
feat(study-room-scraper): ✨ add location routes to studyRooms endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
cokwong committed May 20, 2024
1 parent d721107 commit 10d2f04
Show file tree
Hide file tree
Showing 6 changed files with 54 additions and 44 deletions.
10 changes: 7 additions & 3 deletions apps/api/src/routes/v1/rest/studyRooms/+endpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { studyLocations } from "libs/uc-irvine-lib/src/spaces";
import { ZodError } from "zod";

import { aggregateStudyRooms } from "./lib";
import { Query, QuerySchema } from "./schema";
import { QuerySchema } from "./schema";

export const GET = createHandler(async (event, context, res) => {
const headers = event.headers;
Expand All @@ -13,8 +13,12 @@ export const GET = createHandler(async (event, context, res) => {
const parsedQuery = QuerySchema.parse(query);
if (!studyLocations[parsedQuery.location]) {
return res.createErrorResult(404, `Location ${parsedQuery.location} not found`, requestId);
}
const studyRooms = await aggregateStudyRooms(parsedQuery.location, parsedQuery.start, parsedQuery.end)
}
const studyRooms = await aggregateStudyRooms(
parsedQuery.location,
parsedQuery.start,
parsedQuery.end,
);
return res.createOKResult(studyRooms, headers, requestId);
} catch (e) {
if (e instanceof ZodError) {
Expand Down
19 changes: 14 additions & 5 deletions apps/api/src/routes/v1/rest/studyRooms/{id}/+endpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,19 @@ import { ZodError } from "zod";

import { aggregateStudyRooms } from "../lib";

import { Query, QuerySchema } from "./schema";
import { QuerySchema } from "./schema";

export const GET = createHandler(async (event, context, res) => {
const headers = event.headers;
const query = event.queryStringParameters;
const requestId = context.awsRequestId;
const { id } = event.pathParameters ?? {};
if (id == null) return res.createErrorResult(400, "Location not provided", requestId);
try {
switch (id) {
case "all":
case null:
case undefined:
return res.createErrorResult(400, "Location not provided", requestId);
case "all": {
const parsedQuery = QuerySchema.parse(query);
return res.createOKResult(
await Promise.all(
Expand All @@ -25,8 +27,15 @@ export const GET = createHandler(async (event, context, res) => {
headers,
requestId,
);
default:
return res.createErrorResult(400, "Invalid endpoint", requestId);
}
default: {
if (studyLocations[id]) {
const parsedQuery = QuerySchema.parse(query);
const studyRooms = await aggregateStudyRooms(id, parsedQuery.start, parsedQuery.end);
return res.createOKResult(studyRooms, headers, requestId);
}
return res.createErrorResult(400, `Location ${id} not found`, requestId);
}
}
} catch (e) {
if (e instanceof ZodError) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ The study rooms endpoint allows users to get information and availability of stu

## Query parameters

#### `location` string <span style={{ color: "#ff86b4" }}>Required</span>
#### `location` string

The location of the study rooms to query. Five locations are available to query:

Expand All @@ -38,7 +38,11 @@ The end date of time slots to query. YYYY-MM-DD format.
<TabItem value="bash" label="cURL">

```bash
curl "https://api-next.peterportal.org/v1/rest/studyRooms?location=Science&start=2024-04-26&end=2024-04-30""
curl "https://api-next.peterportal.org/v1/rest/studyRooms/Science?start=2024-04-26&end=2024-04-30"
```

```bash
curl "https://api-next.peterportal.org/v1/rest/studyRooms?location=Science&start=2024-04-26&end=2024-04-30"
```

</TabItem>
Expand Down
8 changes: 1 addition & 7 deletions libs/uc-irvine-lib/src/spaces/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,9 @@ export async function getStudySpaces(lid: string, start: string, end: string) {
const headers = {
Referer: `${LIB_SPACE_URL}?lid=${lid}`,
};
const params = new URLSearchParams();
params.append("lid", lid);
params.append("gid", "0");
params.append("start", start);
params.append("end", end);
params.append("pageSize", "18"); // pageSize doesn't seem to affect query but is required
return await fetch(LIB_SPACE_AVAILABILITY_URL, {
method: "POST",
headers: headers,
body: params,
body: new URLSearchParams({ lid, gid: "0", start, end, pageSize: "18" }),
}).then((res) => res.json());
}
4 changes: 2 additions & 2 deletions tools/study-room-scraper/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { PrismaClient } from "@libs/db";
import type { StudyLocation, StudyRoom } from "@peterportal-api/types";
import type { StudyRoom } from "@peterportal-api/types";

import { scrapeStudyLocations } from "./study-room-scraper";

const prisma = new PrismaClient();

async function main() {
const studyLocations: { [id: string]: StudyLocation } = await scrapeStudyLocations();
const studyLocations = await scrapeStudyLocations();
const studyLocationInfo = Object.values(studyLocations).map((location) => {
return prisma.studyLocation.create({
data: {
Expand Down
49 changes: 24 additions & 25 deletions tools/study-room-scraper/src/study-room-scraper.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import type { StudyRoom, StudyLocation } from "@peterportal-api/types";
import { load } from "cheerio";
import { load, Cheerio, Element, CheerioAPI } from "cheerio";
import fetch from "cross-fetch";
import { studyLocations, getStudySpaces } from "libs/uc-irvine-lib/src/spaces";
import * as winston from "winston";

const ROOM_SPACE_URL = "https://spaces.lib.uci.edu/space";

type StudyLocations = {
[id: string]: StudyLocation;
};
type StudyLocations = Record<string, StudyLocation>;

const logger = winston.createLogger({
level: "info",
Expand All @@ -20,27 +18,33 @@ const logger = winston.createLogger({
transports: [new winston.transports.Console()],
});

function processGML(descriptionHeader: Cheerio<Element>, $: CheerioAPI): string {
let descriptionText = "";
descriptionHeader.find("p").each(function () {
let paraText = $(this).text().trim();
if (paraText.includes("\n")) {
paraText = paraText.replaceAll("\n", ", ");
if (!paraText.endsWith(":")) {
paraText += ". ";
}
}
descriptionText += paraText + " ";
});
descriptionText = descriptionText.replace(/\s{2,}/g, " ").trim();
descriptionText = descriptionText.replace(/\s+,/g, ",");
descriptionText = descriptionText.replace(/\.\s*\./g, ".");
descriptionText = descriptionText.replace(".,", ".");
return descriptionText;
}

function processDescription(
descriptionHeader: cheerio.Cheerio,
descriptionHeader: Cheerio<Element>,
location: string,
$: cheerio.Root,
$: CheerioAPI,
): string {
let descriptionText = "";
if (location === "Grunigen Medical Library") {
descriptionHeader.find("p").each(function () {
let paraText = $(this).text().trim();
if (paraText.includes("\n")) {
paraText = paraText.replaceAll("\n", ", ");
if (!paraText.endsWith(":")) {
paraText += ". ";
}
}
descriptionText += paraText + " ";
});
descriptionText = descriptionText.replace(/\s{2,}/g, " ").trim();
descriptionText = descriptionText.replace(/\s+,/g, ",");
descriptionText = descriptionText.replace(/\.\s*\./g, ".");
descriptionText = descriptionText.replace(".,", ".");
descriptionText = processGML(descriptionHeader, $);
} else {
const descriptionParts: string[] = [];
descriptionHeader.contents().each((_, content) => {
Expand Down Expand Up @@ -164,8 +168,3 @@ export async function scrapeStudyLocations(): Promise<StudyLocations> {
}
return studyLocationsMap;
}
getRoomInfo("44696");
getRoomInfo("116383");
getRoomInfo("117634");
getRoomInfo("120645");
getRoomInfo("51792");

0 comments on commit 10d2f04

Please sign in to comment.