Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add rest and graphql endpoints for study rooms #29

Merged
merged 21 commits into from
Nov 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
fb93afb
added rest and graphql endpoints for study rooms
Nov 12, 2024
4365276
removed mistake from contributing
sanskarm7 Nov 13, 2024
1211e8f
Merge branch 'main' into study-rooms-endpoint
sanskarm7 Nov 13, 2024
d84f594
updated study rooms based on PR comments
Nov 22, 2024
c88d9bc
feat(websoc-scraper): only associate primary instructor(s) with section
ecxyzzy Nov 13, 2024
cf9dd49
feat: api key management interface (#22)
andrew-wang0 Nov 16, 2024
4371ec7
fix: types for keySchema.resources [skip ci]
ecxyzzy Nov 16, 2024
aaf2b74
fix(deps): downgrade to next14, fix versioning issues with authjs
ecxyzzy Nov 17, 2024
98a08be
feat: default light mode for api key management interface (#35)
andrew-wang0 Nov 17, 2024
b440f2c
feat: deploy key manager to AWS (#36)
ecxyzzy Nov 17, 2024
17ea2ad
feat: add loading styling to api manager (#38)
andrew-wang0 Nov 17, 2024
3474cbc
fix: websoc endpoint not returning some courses
ecxyzzy Nov 18, 2024
18b95fe
fix: resolve issue with rate limit override
ecxyzzy Nov 18, 2024
5a06686
fix: aggregate grades returning null on not found
ecxyzzy Nov 18, 2024
d371c87
fix: websoc scraper final exam date
ecxyzzy Nov 20, 2024
75ae547
refactor: clean up middleware
ecxyzzy Nov 20, 2024
d7fb54d
chore: update wrangler, autogen DO typedef
ecxyzzy Nov 20, 2024
85f4bba
chore: enable cors on openapi spec
ecxyzzy Nov 21, 2024
7048db6
revert: cors'd spec not working in prod
ecxyzzy Nov 21, 2024
bf4a68f
Merge branch 'main' into study-rooms-endpoint
ecxyzzy Nov 23, 2024
f0f8b31
chore(deps): downgrade wrangler to 3.85.0
ecxyzzy Nov 23, 2024
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
2 changes: 1 addition & 1 deletion apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,6 @@
"devDependencies": {
"@packages/key-types": "workspace:*",
"@types/node": "20.8.3",
"wrangler": "3.88.0"
"wrangler": "3.85.0"
}
}
2 changes: 2 additions & 0 deletions apps/api/src/graphql/resolvers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { coursesResolvers } from "$graphql/resolvers/courses";
import { enrollmentHistoryResolvers } from "$graphql/resolvers/enrollment-history";
import { gradesResolvers } from "$graphql/resolvers/grades";
import { instructorsResolvers } from "$graphql/resolvers/instructors";
import { studyRoomsResolvers } from "$graphql/resolvers/study-rooms";
import { larcResolvers } from "$graphql/resolvers/larc.ts";
import { searchResolvers } from "$graphql/resolvers/search";
import { websocResolvers } from "$graphql/resolvers/websoc";
Expand All @@ -18,5 +19,6 @@ export const resolvers = mergeResolvers([
searchResolvers,
websocResolvers,
weekResolvers,
studyRoomsResolvers,
larcResolvers,
]);
23 changes: 23 additions & 0 deletions apps/api/src/graphql/resolvers/study-rooms.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { GraphQLContext } from "$graphql/graphql-context";
import { studyRoomsQuerySchema } from "$schema";
import { StudyRoomsService } from "$services";
import { GraphQLError } from "graphql/error";

export const studyRoomsResolvers = {
Query: {
studyRoom: async (_: unknown, { id }: { id: string }, { db }: GraphQLContext) => {
const service = new StudyRoomsService(db);
const res = await service.getStudyRoomById(id);
if (!res)
throw new GraphQLError(`Study room '${id}' not found`, {
extensions: { code: "NOT_FOUND" },
});
return res;
},
studyRooms: async (_: unknown, args: { query?: unknown }, { db }: GraphQLContext) => {
const service = new StudyRoomsService(db);
const validatedQuery = args.query ? studyRoomsQuerySchema.parse(args.query) : {};
return await service.getStudyRooms(validatedQuery);
},
},
};
2 changes: 2 additions & 0 deletions apps/api/src/graphql/schema/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { enrollmentHistorySchema } from "./enrollment-history";
import { enums } from "./enums";
import { gradesSchema } from "./grades";
import { instructorsSchema } from "./instructors";
import { studyRoomsGraphQLSchema } from "./study-rooms";
import { larcSchema } from "./larc";
import { searchSchema } from "./search";
import { websocSchema } from "./websoc";
Expand Down Expand Up @@ -39,5 +40,6 @@ export const typeDefs = mergeTypeDefs([
searchSchema,
websocSchema,
weekSchema,
studyRoomsGraphQLSchema,
larcSchema,
]);
31 changes: 31 additions & 0 deletions apps/api/src/graphql/schema/study-rooms.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
export const studyRoomsGraphQLSchema = `#graphql
type Slot {
studyRoomId: String!
start: String!
end: String!
isAvailable: Boolean!
}

type StudyRoom {
id: String!
name: String!
capacity: Int!
location: String!
description: String
directions: String
techEnhanced: Boolean!
slots: [Slot]
}

input StudyRoomsQuery {
location: String
capacityMin: Int
capacityMax: Int
isTechEnhanced: Boolean
}

extend type Query {
studyRoom(id: String!): StudyRoom!
studyRooms(query: StudyRoomsQuery): [StudyRoom!]!
}
`;
2 changes: 2 additions & 0 deletions apps/api/src/rest/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { gradesRouter } from "./routes/grades";
import { instructorsRouter } from "./routes/instructors";
import { larcRouter } from "./routes/larc.ts";
import { pingRouter } from "./routes/ping";
import { studyRoomsRouter } from "./routes/study-rooms";
import { searchRouter } from "./routes/search";
import { websocRouter } from "./routes/websoc";
import { weekRouter } from "./routes/week";
Expand All @@ -22,6 +23,7 @@ restRouter.route("/ping", pingRouter);
restRouter.route("/search", searchRouter);
restRouter.route("/websoc", websocRouter);
restRouter.route("/week", weekRouter);
restRouter.route("/studyRooms", studyRoomsRouter);
restRouter.route("/larc", larcRouter);

export { restRouter };
90 changes: 90 additions & 0 deletions apps/api/src/rest/routes/study-rooms.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { defaultHook } from "$hooks";
import {
errorSchema,
responseSchema,
studyRoomSchema,
studyRoomsPathSchema,
studyRoomsQuerySchema,
} from "$schema";
import { StudyRoomsService } from "$services";
import type { Bindings } from "$types/bindings";
import { OpenAPIHono, createRoute } from "@hono/zod-openapi";
import { database } from "@packages/db";

const studyRoomsRouter = new OpenAPIHono<{ Bindings: Bindings }>({ defaultHook });

const studyRoomByIdRoute = createRoute({
summary: "Retrieve a study room",
operationId: "studyRoomById",
tags: ["Study Rooms"],
method: "get",
path: "/{id}",
request: { params: studyRoomsPathSchema },
description: "Retrieves a study room by its ID.",
responses: {
200: {
content: { "application/json": { schema: responseSchema(studyRoomSchema) } },
description: "Successful operation",
},
404: {
content: { "application/json": { schema: errorSchema } },
description: "Study room not found",
},
422: {
content: { "application/json": { schema: errorSchema } },
description: "Parameters failed validation",
},
500: {
content: { "application/json": { schema: errorSchema } },
description: "Server error occurred",
},
},
});

const studyRoomsByFiltersRoute = createRoute({
summary: "Retrieve study rooms",
operationId: "studyRoomsByFilters",
tags: ["Study Rooms"],
method: "get",
path: "/",
request: { query: studyRoomsQuerySchema },
description:
"Retrieves study rooms matching the given filters. If no filters are provided, all rooms are returned.",
responses: {
200: {
content: {
"application/json": { schema: responseSchema(studyRoomSchema.array()) },
},
description: "Successful operation",
},
500: {
content: { "application/json": { schema: errorSchema } },
description: "Server error occurred",
},
},
});

// studyRoomsRouter.get("*");

studyRoomsRouter.openapi(studyRoomByIdRoute, async (c) => {
const { id } = c.req.valid("param");
const service = new StudyRoomsService(database(c.env.DB.connectionString));
const res = await service.getStudyRoomById(id);
return res
? c.json({ ok: true, data: studyRoomSchema.parse(res) }, 200)
: c.json({ ok: false, message: `Study room ${id} not found` }, 404);
});

studyRoomsRouter.openapi(studyRoomsByFiltersRoute, async (c) => {
const query = c.req.valid("query") || {}; // no filters required
const service = new StudyRoomsService(database(c.env.DB.connectionString));
return c.json(
{
ok: true,
data: studyRoomSchema.array().parse(await service.getStudyRooms(query)),
},
200,
);
});

export { studyRoomsRouter };
1 change: 1 addition & 0 deletions apps/api/src/schema/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export * from "./instructors";
export * from "./search";
export * from "./websoc";
export * from "./week";
export * from "./study-rooms";
30 changes: 30 additions & 0 deletions apps/api/src/schema/study-rooms.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { z } from "@hono/zod-openapi";

export const slotSchema = z.object({
studyRoomId: z.string(),
start: z.string().openapi({ format: "date-time" }),
end: z.string().openapi({ format: "date-time" }),
isAvailable: z.boolean(),
});

export const studyRoomSchema = z.object({
id: z.string(),
name: z.string(),
capacity: z.number().int(),
location: z.string(),
description: z.string().optional(),
directions: z.string().optional(),
techEnhanced: z.boolean(),
slots: z.array(slotSchema).optional(),
sanskarm7 marked this conversation as resolved.
Show resolved Hide resolved
});

export const studyRoomsPathSchema = z.object({
id: z.string(),
});

export const studyRoomsQuerySchema = z.object({
location: z.string().optional(),
capacityMin: z.coerce.number().int().optional(),
capacityMax: z.coerce.number().int().optional(),
isTechEnhanced: z.coerce.boolean().optional(),
});
1 change: 1 addition & 0 deletions apps/api/src/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export * from "./instructors";
export * from "./search";
export * from "./websoc";
export * from "./week";
export * from "./study-rooms";
30 changes: 30 additions & 0 deletions apps/api/src/services/study-rooms.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type { studyRoomsQuerySchema } from "$schema";
import type { z } from "@hono/zod-openapi";
import type { database } from "@packages/db";
import { and, eq, gte, lte } from "@packages/db/drizzle";
import { studyRoom } from "@packages/db/schema";

type StudyRoomsServiceInput = z.infer<typeof studyRoomsQuerySchema>;

export class StudyRoomsService {
constructor(private readonly db: ReturnType<typeof database>) {}

async getStudyRoomById(id: string) {
const [room] = await this.db.select().from(studyRoom).where(eq(studyRoom.id, id));
return room || null;
}

async getStudyRooms(input: StudyRoomsServiceInput) {
const conditions = [];
if (input.location) conditions.push(eq(studyRoom.location, input.location));
if (input.capacityMin) conditions.push(gte(studyRoom.capacity, input.capacityMin));
if (input.capacityMax) conditions.push(lte(studyRoom.capacity, input.capacityMax));
if (input.isTechEnhanced !== undefined)
conditions.push(eq(studyRoom.techEnhanced, input.isTechEnhanced));

return await this.db
.select()
.from(studyRoom)
.where(conditions.length ? and(...conditions) : undefined);
}
}
2 changes: 1 addition & 1 deletion apps/data-pipeline/larc-scraper/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@
},
"devDependencies": {
"@types/node": "20.8.3",
"wrangler": "3.88.0"
"wrangler": "3.85.0"
}
}
5 changes: 3 additions & 2 deletions apps/data-pipeline/larc-scraper/worker-configuration.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Generated by Wrangler by running `wrangler types --x-include-runtime`

interface Env {
DB: Hyperdrive;
export {};
declare global {
const DB: Hyperdrive;
}
2 changes: 1 addition & 1 deletion apps/data-pipeline/study-location-scraper/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@
"devDependencies": {
"@types/node": "20.8.3",
"domhandler": "5.0.3",
"wrangler": "3.88.0"
"wrangler": "3.85.0"
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Generated by Wrangler by running `wrangler types --x-include-runtime`

interface Env {
DB: Hyperdrive;
export {};
declare global {
const DB: Hyperdrive;
}
2 changes: 1 addition & 1 deletion apps/data-pipeline/websoc-scraper/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@
},
"devDependencies": {
"@types/node": "20.8.3",
"wrangler": "3.88.0"
"wrangler": "3.85.0"
}
}
2 changes: 1 addition & 1 deletion apps/key-manager/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,6 @@
"postcss": "8.4.49",
"tailwindcss": "3.4.15",
"typescript": "5.6.3",
"wrangler": "3.88.0"
"wrangler": "3.85.0"
}
}
Loading