Skip to content

Commit

Permalink
feat: Pins, getSecondarySections, etc.
Browse files Browse the repository at this point in the history
  • Loading branch information
mathhulk committed Oct 17, 2024
1 parent 9c1e9bd commit 4f22d96
Show file tree
Hide file tree
Showing 31 changed files with 642 additions and 135 deletions.
3 changes: 3 additions & 0 deletions apps/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,11 @@
"dependencies": {
"@apollo/server": "^4.10.5",
"@apollo/server-plugin-response-cache": "^4.1.3",
"@apollo/utils.keyvadapter": "^3.1.0",
"@escape.tech/graphql-armor": "^3.0.1",
"@graphql-tools/schema": "^10.0.4",
"@graphql-tools/utils": "^10.3.2",
"@keyv/redis": "^3.0.1",
"@repo/common": "*",
"compression": "^1.7.4",
"connect-redis": "^7.1.1",
Expand All @@ -46,6 +48,7 @@
"graphql-modules": "^2.3.0",
"graphql-type-json": "^0.3.2",
"helmet": "^7.1.0",
"keyv": "^5.1.0",
"lodash": "^4.17.21",
"mongodb": "^6.8.0",
"mongoose": "^8.5.1",
Expand Down
17 changes: 12 additions & 5 deletions apps/backend/src/bootstrap/loaders/apollo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,21 @@ class RedisCache implements KeyValueCache {
}

async get(key: string) {
return (await this.client.get(this.prefix + key)) ?? undefined;
const value = await this.client.get(this.prefix + key);

return value ?? undefined;
}

async set(key: string, value: string) {
// ttl options are intentionally ignored because we will invalidate cache in update script
async set(key: string, value: string | null) {
if (!value) return;

await this.client.set(this.prefix + key, value);
}

async delete(key: string) {
return (await this.client.del(this.prefix + key)) === 1;
const success = await this.client.del(this.prefix + key);

return success === 1;
}
}

Expand All @@ -50,7 +55,9 @@ export default async (redis: RedisClientType) => {
plugins: [
...protection.plugins,
ApolloServerPluginLandingPageLocalDefault({ includeCookies: true }),
ApolloServerPluginCacheControl({ calculateHttpHeaders: false }),
ApolloServerPluginCacheControl({
calculateHttpHeaders: false,
}),
responseCachePlugin(),
],
// TODO(production): Disable introspection in production
Expand Down
8 changes: 6 additions & 2 deletions apps/backend/src/bootstrap/loaders/redis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ import { RedisClientType, createClient } from "redis";
import { config } from "../../config";

export default async (): Promise<RedisClientType> => {
return (await createClient({
const client = createClient({
url: config.redisUri,
}).connect()) as RedisClientType;
});

await client.connect();

return client as RedisClientType;
};
8 changes: 6 additions & 2 deletions apps/backend/src/modules/catalog/resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@ import { CatalogModule } from "./generated-types/module-types";

const resolvers: CatalogModule.Resolvers = {
Query: {
catalog: (_, { term }, __, info) =>
getCatalog(term.year, term.semester, info),
catalog: async (_, { term }, __, info) => {
// const cacheControl = cacheControlFromInfo(info);
// cacheControl.setCacheHint({ maxAge: 300 });

return await getCatalog(term.year, term.semester, info);
},
},
};

Expand Down
20 changes: 8 additions & 12 deletions apps/backend/src/modules/class/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,16 @@ export const getSecondarySections = async (
semester: string,
subject: string,
courseNumber: string,
classNumber: string
number: string
) => {
return await SectionModel.find({
const sections = await SectionModel.find({
"class.course.subjectArea.code": subject,
"class.course.catalogNumber.formatted": courseNumber,
"class.session.term.name": `${year} ${semester}`,
"class.number": classNumber,
"association.primary": false,
})
.lean()
.then((sections) => sections.map(formatSection));
"class.number": { $regex: `^(${number[number.length - 1]}|999)` },
}).lean();

return sections.map(formatSection);
};

export const getPrimarySection = async (
Expand All @@ -51,7 +50,6 @@ export const getPrimarySection = async (
"class.course.catalogNumber.formatted": courseNumber,
"class.session.term.name": `${year} ${semester}`,
"class.number": classNumber,
"association.primary": true,
}).lean();

if (!section) return null;
Expand All @@ -64,15 +62,13 @@ export const getSection = async (
semester: string,
subject: string,
courseNumber: string,
classNumber: string,
sectionNumber: string
number: string
) => {
const section = await SectionModel.findOne({
"class.course.subjectArea.code": subject,
"class.course.catalogNumber.formatted": courseNumber,
"class.session.term.name": `${year} ${semester}`,
"class.number": classNumber,
number: sectionNumber,
number: number,
}).lean();

if (!section) return null;
Expand Down
6 changes: 1 addition & 5 deletions apps/backend/src/modules/class/resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,12 @@ const resolvers: ClassModule.Resolvers = {
return _class as unknown as ClassModule.Class;
},

section: async (
_,
{ subject, courseNumber, classNumber, number, year, semester }
) => {
section: async (_, { subject, courseNumber, number, year, semester }) => {
const section = await getSection(
year,
semester,
subject,
courseNumber,
classNumber,
number
);

Expand Down
4 changes: 0 additions & 4 deletions apps/backend/src/modules/course/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,6 @@ export const getClassesByCourse = async (
};

export const getAssociatedCourses = async (courses: string[]) => {
console.log(courses);

const queries = courses.map((course) => {
const split = course.split(" ");

Expand All @@ -78,8 +76,6 @@ export const getAssociatedCourses = async (courses: string[]) => {
.sort({ fromDate: -1 })
.lean();

console.log(associatedCourses);

return (
associatedCourses
// TODO: Properly filter out duplicates in the query
Expand Down
2 changes: 0 additions & 2 deletions apps/backend/src/modules/schedule/formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@ export const formatSchedule = async (schedule: ScheduleType) => {
classes,
year: schedule.year,
semester: schedule.semester,
beginDate: schedule.beginDate,
endDate: schedule.endDate,
term: null,
events: schedule.events,
} as IntermediateSchedule;
Expand Down
5 changes: 4 additions & 1 deletion apps/frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import Enrollment from "@/app/Enrollment";
import Grades from "@/app/Grades";
import Landing from "@/app/Landing";
import Layout from "@/components/Layout";
import PinsProvider from "@/components/PinsProvider";

const Class = {
Enrollment: lazy(() => import("@/components/Class/Enrollment")),
Expand Down Expand Up @@ -177,7 +178,9 @@ export default function App() {
return (
<ApolloProvider client={client}>
<ThemeProvider>
<RouterProvider router={router} />
<PinsProvider>
<RouterProvider router={router} />
</PinsProvider>
</ThemeProvider>
</ApolloProvider>
);
Expand Down
44 changes: 22 additions & 22 deletions apps/frontend/src/app/Schedules/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,28 +19,28 @@ export default function Schedules() {

if (!user) return <></>;

if (schedules) {
return (
<Container>
<button
onClick={() =>
createSchedule({
name: "Test",
year: 2024,
semester: Semester.Fall,
})
}
>
Create Schedule
</button>
{schedules?.map((schedule) => (
<div key={schedule._id}>
<Link to={schedule._id}>{schedule.name}</Link>
</div>
))}
</Container>
);
if (!schedules) {
return <></>;
}

return <></>;
return (
<Container>
<button
onClick={() =>
createSchedule({
name: "Test",
year: 2024,
semester: Semester.Fall,
})
}
>
Create Schedule
</button>
{schedules?.map((schedule) => (
<div key={schedule._id}>
<Link to={schedule._id}>{schedule.name}</Link>
</div>
))}
</Container>
);
}
48 changes: 42 additions & 6 deletions apps/frontend/src/components/Class/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {
CalendarPlus,
OpenBook,
OpenNewWindow,
Pin,
PinSolid,
SidebarCollapse,
SidebarExpand,
Xmark,
Expand All @@ -30,7 +32,9 @@ import Capacity from "@/components/Capacity";
import CourseDrawer from "@/components/CourseDrawer";
import Units from "@/components/Units";
import ClassContext from "@/contexts/ClassContext";
import { ClassPin } from "@/contexts/PinsContext";
import { useReadClass } from "@/hooks/api/classes/useReadClass";
import usePins from "@/hooks/usePins";
import { IClass, Semester } from "@/lib/api";
import { getExternalLink } from "@/lib/section";

Expand Down Expand Up @@ -123,6 +127,8 @@ export default function Class({
onClose,
dialog,
}: ClassProps) {
const { pins, addPin, removePin } = usePins();

const location = useLocation();

// TODO: Bookmarks
Expand All @@ -142,11 +148,33 @@ export default function Class({

const _class = useMemo(() => providedClass ?? data, [data, providedClass]);

const pin = useMemo(() => {
if (!_class) return;

const { year, semester, subject, courseNumber, number } = _class;

const id = `${year}-${semester}-${subject}-${courseNumber}-${number}`;

return {
id,
type: "class",
data: {
year,
semester,
subject,
courseNumber,
number,
},
} as ClassPin;
}, [year, semester, subject, courseNumber, number]);

const pinned = useMemo(() => pins.some((p) => p.id === pin?.id), [pins, pin]);

if (loading) {
return <></>;
}

if (!_class) {
if (!_class || !pin) {
return <></>;
}

Expand All @@ -163,9 +191,7 @@ export default function Class({
</IconButton>
</Tooltip>
)}
<Tooltip
content={bookmarked ? "Remove bookmark" : "Bookmark course"}
>
<Tooltip content={bookmarked ? "Remove bookmark" : "Bookmark"}>
<IconButton
className={classNames(styles.bookmark, {
[styles.active]: bookmarked,
Expand All @@ -175,7 +201,17 @@ export default function Class({
{bookmarked ? <BookmarkSolid /> : <Bookmark />}
</IconButton>
</Tooltip>
<Tooltip content="Add class to schedule">
<Tooltip content={pinned ? "Remove pin" : "Pin"}>
<IconButton
className={classNames(styles.bookmark, {
[styles.active]: pinned,
})}
onClick={() => (pinned ? removePin(pin) : addPin(pin))}
>
{pinned ? <PinSolid /> : <Pin />}
</IconButton>
</Tooltip>
<Tooltip content="Add to schedule">
<IconButton>
<CalendarPlus />
</IconButton>
Expand Down Expand Up @@ -254,7 +290,7 @@ export default function Class({
</div>
</div>
<h1 className={styles.heading}>
{_class.subject} {_class.courseNumber} {_class.number}
{_class.subject} {_class.courseNumber} #{_class.number}
</h1>
<p className={styles.description}>
{_class.title || _class.course.title}
Expand Down
Loading

0 comments on commit 4f22d96

Please sign in to comment.