Skip to content
Open
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"private": true,
"scripts": {
"dev": "cross-env NODE_OPTIONS='--inspect' next dev",
"build": "next build",
"build": "cross-env NODE_OPTIONS='--max-old-space-size=4096' next build",
"start": "next start",
"format": "prettier --write .",
"format:check": "prettier --check .",
Expand Down
84 changes: 83 additions & 1 deletion src/app/api/autocomplete/route.ts
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really like the idea of the caching in this file. I don't know if I'm doing a great job of reviewing it tho because I haven't really seen anything like this before. @nl32 do you think you might be able to take a look

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking into this further myself, I like the caching idea. It does have a very large first load time of like 4 of so seconds for me which I am guessing is mostly fetching all the sections. Do you think we could combine your original approach and this new one by fetching hasNotes for only the results being returned and caching only those results instead of the whole DB? This would require each result to have it's own TTL. Can we also do a TTL of 1 minute so it's quicker to let you search for a course after uploading a note? Lmk if that makes sense

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep that makes sense sorry for the delay got caught in studying for exams

Original file line number Diff line number Diff line change
@@ -1,12 +1,65 @@
import { and, eq } from 'drizzle-orm';
import { NextResponse } from 'next/server';
import autocompleteGraph from 'src/data/autocomplete_graph.json';
import { db } from '@src/server/db';
import { file } from '@src/server/db/schema/file';
import { section } from '@src/server/db/schema/section';
import { getGraph, searchAutocomplete } from '@src/utils/autocomplete';
import type { GenericFetchedData } from '@src/utils/GenericFetchedData';
import { type SearchQueryWithTotalStudents } from '@src/utils/SearchQuery';

const graph = getGraph(autocompleteGraph as object);

export function GET(request: Request) {
const CACHE_TTL_MS = 1000 * 60 * 1; // 1 minute

declare global {
var __autocompleteCache:
| Map<
string,
{
hasNotes: boolean;
timestamp: number;
}
>
| undefined;
}

function getCache() {
if (!globalThis.__autocompleteCache) {
globalThis.__autocompleteCache = new Map();
}
return globalThis.__autocompleteCache;
}

async function checkHasNotesForCourse(
prefix: string,
number: string,
): Promise<boolean> {
const result = await db
.select({ id: section.id })
.from(section)
.innerJoin(file, eq(file.sectionId, section.id))
.where(and(eq(section.prefix, prefix), eq(section.number, number)))
.limit(1);
return result.length > 0;
}

async function checkHasNotesForProf(
profFirst: string,
profLast: string,
): Promise<boolean> {
const result = await db
.select({ id: section.id })
.from(section)
.innerJoin(file, eq(file.sectionId, section.id))
.where(
and(eq(section.profFirst, profFirst), eq(section.profLast, profLast)),
)
.limit(1);
return result.length > 0;
}

export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const input = searchParams.get('input');
if (typeof input !== 'string') {
Expand All @@ -30,6 +83,35 @@ export function GET(request: Request) {

const results = searchAutocomplete(graph, input, limit, searchBy);

const cache = getCache();

for (const res of results) {
if (res.prefix && res.number) {
const key = `course:${res.prefix.toLowerCase()}|${res.number}`;
const cached = cache.get(key);
if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) {
res.hasNotes = cached.hasNotes;
} else {
const hasNotes = await checkHasNotesForCourse(res.prefix, res.number);
res.hasNotes = hasNotes;
cache.set(key, { hasNotes, timestamp: Date.now() });
}
} else if (res.profFirst && res.profLast) {
const key = `prof:${res.profFirst.toLowerCase()}|${res.profLast.toLowerCase()}`;
const cached = cache.get(key);
if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) {
res.hasNotes = cached.hasNotes;
} else {
const hasNotes = await checkHasNotesForProf(
res.profFirst,
res.profLast,
);
res.hasNotes = hasNotes;
cache.set(key, { hasNotes, timestamp: Date.now() });
}
}
}

return NextResponse.json(
{
state: 'done',
Expand Down
Loading