From bd32661f955fb022a84d29e8d68ad1b912d4be43 Mon Sep 17 00:00:00 2001 From: Abhinav Ganeshan Date: Thu, 2 Oct 2025 03:13:00 +0530 Subject: [PATCH 1/2] refactor: logical grouping of files in lib and improved separation of concerns --- src/app/api/course-list/route.ts | 2 +- src/app/api/paper-by-id/[id]/route.ts | 2 +- src/app/api/papers/count/route.ts | 2 +- src/app/api/papers/route.ts | 41 +++++--------------------- src/app/api/related-subject/route.ts | 12 ++++---- src/app/api/request/route.ts | 2 +- src/app/api/selected-papers/route.ts | 2 +- src/app/api/subscribe/route.ts | 33 ++------------------- src/app/api/upcoming-papers/route.ts | 16 +++++----- src/app/api/upload/route.ts | 6 ++-- src/app/api/user-papers/route.ts | 25 ++++------------ src/app/upload/page.tsx | 4 +-- src/lib/{ => database}/mongoose.ts | 2 +- src/lib/services/google-sheets.ts | 30 +++++++++++++++++++ src/lib/services/paper-transform.ts | 17 +++++++++++ src/lib/{storage.ts => storage/gcp.ts} | 3 +- src/lib/{ => storage}/pdf.ts | 2 +- src/lib/{utils.ts => utils/index.ts} | 2 +- src/lib/utils/paper-aggregation.ts | 28 ++++++++++++++++++ src/lib/utils/regex.ts | 3 ++ src/lib/utils/slot-calculation.ts | 10 +++++++ 21 files changed, 129 insertions(+), 115 deletions(-) rename src/lib/{ => database}/mongoose.ts (99%) create mode 100644 src/lib/services/google-sheets.ts create mode 100644 src/lib/services/paper-transform.ts rename src/lib/{storage.ts => storage/gcp.ts} (97%) rename src/lib/{ => storage}/pdf.ts (96%) rename src/lib/{utils.ts => utils/index.ts} (98%) create mode 100644 src/lib/utils/paper-aggregation.ts create mode 100644 src/lib/utils/regex.ts create mode 100644 src/lib/utils/slot-calculation.ts diff --git a/src/app/api/course-list/route.ts b/src/app/api/course-list/route.ts index 09d171ae..31ef5c0a 100644 --- a/src/app/api/course-list/route.ts +++ b/src/app/api/course-list/route.ts @@ -1,5 +1,5 @@ import { NextResponse } from "next/server"; -import { connectToDatabase } from "@/lib/mongoose"; +import { connectToDatabase } from "@/lib/database/mongoose"; import { Course } from "@/db/course"; export const dynamic = "force-dynamic"; diff --git a/src/app/api/paper-by-id/[id]/route.ts b/src/app/api/paper-by-id/[id]/route.ts index 09a0ebbb..aced225c 100644 --- a/src/app/api/paper-by-id/[id]/route.ts +++ b/src/app/api/paper-by-id/[id]/route.ts @@ -1,5 +1,5 @@ import { NextResponse } from "next/server"; -import { connectToDatabase } from "@/lib/mongoose"; +import { connectToDatabase } from "@/lib/database/mongoose"; import Paper from "@/db/papers"; import { Types } from "mongoose"; diff --git a/src/app/api/papers/count/route.ts b/src/app/api/papers/count/route.ts index 2849c825..2225ff3a 100644 --- a/src/app/api/papers/count/route.ts +++ b/src/app/api/papers/count/route.ts @@ -1,5 +1,5 @@ import { NextResponse } from "next/server"; -import { connectToDatabase } from "@/lib/mongoose"; +import { connectToDatabase } from "@/lib/database/mongoose"; import CourseCount from "@/db/course"; export const dynamic = "force-dynamic"; diff --git a/src/app/api/papers/route.ts b/src/app/api/papers/route.ts index fc4aa808..5b2ae2fc 100644 --- a/src/app/api/papers/route.ts +++ b/src/app/api/papers/route.ts @@ -1,7 +1,9 @@ import { NextResponse, type NextRequest } from "next/server"; -import { connectToDatabase } from "@/lib/mongoose"; +import { connectToDatabase } from "@/lib/database/mongoose"; import Paper from "@/db/papers"; import { type IPaper } from "@/interface"; +import { escapeRegExp } from "@/lib/utils/regex"; +import { extractUniqueValues } from "@/lib/utils/paper-aggregation"; export const dynamic = "force-dynamic"; @@ -10,10 +12,6 @@ export async function GET(req: NextRequest) { await connectToDatabase(); const url = req.nextUrl.searchParams; const subject = url.get("subject"); - const escapeRegExp = (text: string) => { - return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - }; - const escapedSubject = escapeRegExp(subject ?? ""); if (!subject) { return NextResponse.json( @@ -22,42 +20,17 @@ export async function GET(req: NextRequest) { ); } + const escapedSubject = escapeRegExp(subject); const papers: IPaper[] = await Paper.find({ subject: { $regex: new RegExp(`${escapedSubject}`, "i") }, }); - if (papers.length === 0) { - return NextResponse.json( - { - papers, - unique_years: [], - unique_slots: [], - unique_exams: [], - unique_campuses: [], - unique_semesters: [], - }, - { status: 200 }, - ); - } - - const unique_years = Array.from(new Set(papers.map((paper) => paper.year))); - const unique_slots = Array.from(new Set(papers.map((paper) => paper.slot))); - const unique_exams = Array.from(new Set(papers.map((paper) => paper.exam))); - const unique_campuses = Array.from( - new Set(papers.map((paper) => paper.campus)), - ); - const unique_semesters = Array.from( - new Set(papers.map((paper) => paper.semester)), - ); + const uniqueValues = extractUniqueValues(papers); return NextResponse.json( { papers, - unique_years, - unique_slots, - unique_exams, - unique_campuses, - unique_semesters, + ...uniqueValues, }, { status: 200 }, ); @@ -67,4 +40,4 @@ export async function GET(req: NextRequest) { { status: 500 }, ); } -} +} \ No newline at end of file diff --git a/src/app/api/related-subject/route.ts b/src/app/api/related-subject/route.ts index 1640f9ec..77800cbf 100644 --- a/src/app/api/related-subject/route.ts +++ b/src/app/api/related-subject/route.ts @@ -1,7 +1,8 @@ import { NextResponse, type NextRequest } from "next/server"; -import { connectToDatabase } from "@/lib/mongoose"; +import { connectToDatabase } from "@/lib/database/mongoose"; import { IRelatedSubject } from "@/interface"; import RelatedSubject from "@/db/relatedSubjects"; +import { escapeRegExp } from "@/lib/utils/regex"; export const dynamic = "force-dynamic"; @@ -10,10 +11,6 @@ export async function GET(req: NextRequest) { await connectToDatabase(); const url = req.nextUrl.searchParams; const subject = url.get("subject"); - const escapeRegExp = (text: string) => { - return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - }; - const escapedSubject = escapeRegExp(subject ?? ""); if (!subject) { return NextResponse.json( @@ -21,9 +18,12 @@ export async function GET(req: NextRequest) { { status: 400 }, ); } + + const escapedSubject = escapeRegExp(subject); const subjects: IRelatedSubject[] = await RelatedSubject.find({ subject: { $regex: new RegExp(`${escapedSubject}`, "i") }, }); + console.log("realted", subjects); return NextResponse.json( @@ -38,4 +38,4 @@ export async function GET(req: NextRequest) { { status: 500 }, ); } -} +} \ No newline at end of file diff --git a/src/app/api/request/route.ts b/src/app/api/request/route.ts index 3c5920d5..48c93022 100644 --- a/src/app/api/request/route.ts +++ b/src/app/api/request/route.ts @@ -1,5 +1,5 @@ import { NextResponse } from "next/server"; -import { connectToDatabase } from "@/lib/mongoose"; +import { connectToDatabase } from "@/lib/database/mongoose"; import PaperRequest from "@/db/paperRequest"; export async function POST(req: Request) { diff --git a/src/app/api/selected-papers/route.ts b/src/app/api/selected-papers/route.ts index fd972d25..5c822f6b 100644 --- a/src/app/api/selected-papers/route.ts +++ b/src/app/api/selected-papers/route.ts @@ -1,5 +1,5 @@ import { NextResponse } from "next/server"; -import { connectToDatabase } from "@/lib/mongoose"; +import { connectToDatabase } from "@/lib/database/mongoose"; import Paper from "@/db/papers"; export const dynamic = "force-dynamic"; diff --git a/src/app/api/subscribe/route.ts b/src/app/api/subscribe/route.ts index cc08c95e..2848b59f 100644 --- a/src/app/api/subscribe/route.ts +++ b/src/app/api/subscribe/route.ts @@ -1,34 +1,5 @@ import { NextResponse } from "next/server"; -import { google, sheets_v4 } from "googleapis"; -import { JWT } from "google-auth-library"; - -const SCOPES = ["https://www.googleapis.com/auth/spreadsheets"]; -const SHEET_ID = process.env.SHEET_ID; - -async function getAuth(): Promise { - return new google.auth.JWT({ - email: process.env.GOOGLE_CLIENT_EMAIL, - key: process.env.GOOGLE_PRIVATE_KEY?.replace(/\\n/g, "\n"), - scopes: SCOPES, - }); -} - -async function appendEmailToSheet(email: string) { - const authClient = await getAuth(); - const sheets: sheets_v4.Sheets = google.sheets({ - version: "v4", - auth: authClient, - }); - - await sheets.spreadsheets.values.append({ - spreadsheetId: SHEET_ID, - range: "Sheet1!A:A", - valueInputOption: "RAW", - requestBody: { - values: [[email]], - }, - }); -} +import { appendEmailToSheet } from "@/lib/services/google-sheets"; export async function POST(req: Request) { try { @@ -44,4 +15,4 @@ export async function POST(req: Request) { console.error("Error adding email:", error); return NextResponse.json({ error: "Failed to add email" }, { status: 500 }); } -} +} \ No newline at end of file diff --git a/src/app/api/upcoming-papers/route.ts b/src/app/api/upcoming-papers/route.ts index 8b426fab..72d52e9a 100644 --- a/src/app/api/upcoming-papers/route.ts +++ b/src/app/api/upcoming-papers/route.ts @@ -1,7 +1,8 @@ import { NextResponse } from "next/server"; -import { connectToDatabase } from "@/lib/mongoose"; +import { connectToDatabase } from "@/lib/database/mongoose"; import UpcomingSlot from "@/db/upcoming-slot"; import UpcomingSubject from "@/db/upcoming-paper"; +import { calculateCorrespondingSlots } from "@/lib/utils/slot-calculation"; export const dynamic = "force-dynamic"; @@ -10,6 +11,7 @@ export async function GET() { await connectToDatabase(); const upcomingSlot = await UpcomingSlot.find(); const slot = upcomingSlot[0]?.slot; + if (!slot) { return NextResponse.json( { @@ -18,16 +20,12 @@ export async function GET() { { status: 404 }, ); } - const nextSlot = String.fromCharCode(slot.charCodeAt(0) + 1); - const correspondingSlots = [ - slot + "1", - slot + "2", - nextSlot + "1", - nextSlot + "2", - ]; + + const correspondingSlots = calculateCorrespondingSlots(slot); const selectedSubjects = await UpcomingSubject.find({ slots: { $in: correspondingSlots }, }); + if (selectedSubjects.length === 0) { return NextResponse.json( { @@ -49,4 +47,4 @@ export async function GET() { { status: 500 }, ); } -} +} \ No newline at end of file diff --git a/src/app/api/upload/route.ts b/src/app/api/upload/route.ts index 4164ebe4..2fad98e7 100644 --- a/src/app/api/upload/route.ts +++ b/src/app/api/upload/route.ts @@ -1,8 +1,8 @@ import { NextResponse } from "next/server"; -import { connectToDatabase } from "@/lib/mongoose"; +import { connectToDatabase } from "@/lib/database/mongoose"; import { PaperAdmin } from "@/db/papers"; -import { createPDFfromImages } from "@/lib/pdf"; -import { uploadPDF, uploadThumbnail } from "@/lib/storage"; +import { createPDFfromImages } from "@/lib/storage/pdf"; +import { uploadPDF, uploadThumbnail } from "@/lib/storage/gcp"; export const runtime = "nodejs"; diff --git a/src/app/api/user-papers/route.ts b/src/app/api/user-papers/route.ts index 9f1ba6b3..f3a9c674 100644 --- a/src/app/api/user-papers/route.ts +++ b/src/app/api/user-papers/route.ts @@ -1,11 +1,11 @@ import { NextResponse } from "next/server"; -import { connectToDatabase } from "@/lib/mongoose"; +import { connectToDatabase } from "@/lib/database/mongoose"; import Paper from "@/db/papers"; -import { StoredSubjects, TransformedPaper } from "@/interface"; +import { StoredSubjects } from "@/interface"; +import { transformPapersToSubjectSlots } from "@/lib/services/paper-transform"; export const dynamic = "force-dynamic"; - export async function POST(req: Request) { try { await connectToDatabase(); @@ -17,22 +17,7 @@ export async function POST(req: Request) { console.log("Fetched user papers:", usersPapers); - const transformedPapers = usersPapers.reduce( - (acc, paper) => { - const existing = acc.find((item) => item.subject === paper.subject); - - if (existing) { - if (!existing.slots.includes(paper.slot)) { - existing.slots.push(paper.slot); - } - } else { - acc.push({ subject: paper.subject, slots: [paper.slot] }); - } - - return acc; - }, - [], - ); + const transformedPapers = transformPapersToSubjectSlots(usersPapers); return NextResponse.json(transformedPapers, { status: 200, @@ -46,4 +31,4 @@ export async function POST(req: Request) { { status: 500 }, ); } -} +} \ No newline at end of file diff --git a/src/app/upload/page.tsx b/src/app/upload/page.tsx index 1260f84f..d5cf150a 100644 --- a/src/app/upload/page.tsx +++ b/src/app/upload/page.tsx @@ -26,7 +26,7 @@ import Dropzone from "react-dropzone"; import { Upload, XIcon } from "lucide-react"; import { getDocument, GlobalWorkerOptions } from "pdfjs-dist"; -GlobalWorkerOptions.workerSrc = +GlobalWorkerOptions.workerSrc = "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.8.69/pdf.worker.min.mjs"; interface APIResponse { @@ -288,7 +288,7 @@ export default function Page() { setIsUploading(false); } }; - + return (
diff --git a/src/lib/mongoose.ts b/src/lib/database/mongoose.ts similarity index 99% rename from src/lib/mongoose.ts rename to src/lib/database/mongoose.ts index 6030ba14..5e3b2e7f 100644 --- a/src/lib/mongoose.ts +++ b/src/lib/database/mongoose.ts @@ -39,4 +39,4 @@ export async function connectToDatabase() { } return cached?.conn; -} +} \ No newline at end of file diff --git a/src/lib/services/google-sheets.ts b/src/lib/services/google-sheets.ts new file mode 100644 index 00000000..0adb583b --- /dev/null +++ b/src/lib/services/google-sheets.ts @@ -0,0 +1,30 @@ +import { google, sheets_v4 } from "googleapis"; +import { JWT } from "google-auth-library"; + +const SCOPES = ["https://www.googleapis.com/auth/spreadsheets"]; +const SHEET_ID = process.env.SHEET_ID; + +export async function getGoogleSheetsAuth(): Promise { + return new google.auth.JWT({ + email: process.env.GOOGLE_CLIENT_EMAIL, + key: process.env.GOOGLE_PRIVATE_KEY?.replace(/\\n/g, "\n"), + scopes: SCOPES, + }); +} + +export async function appendEmailToSheet(email: string): Promise { + const authClient = await getGoogleSheetsAuth(); + const sheets: sheets_v4.Sheets = google.sheets({ + version: "v4", + auth: authClient, + }); + + await sheets.spreadsheets.values.append({ + spreadsheetId: SHEET_ID, + range: "Sheet1!A:A", + valueInputOption: "RAW", + requestBody: { + values: [[email]], + }, + }); +} \ No newline at end of file diff --git a/src/lib/services/paper-transform.ts b/src/lib/services/paper-transform.ts new file mode 100644 index 00000000..268a0a07 --- /dev/null +++ b/src/lib/services/paper-transform.ts @@ -0,0 +1,17 @@ +import { type IPaper, type TransformedPaper } from "../../interface"; + +export function transformPapersToSubjectSlots(papers: IPaper[]): TransformedPaper[] { + return papers.reduce((acc, paper) => { + const existing = acc.find((item) => item.subject === paper.subject); + + if (existing) { + if (!existing.slots.includes(paper.slot)) { + existing.slots.push(paper.slot); + } + } else { + acc.push({ subject: paper.subject, slots: [paper.slot] }); + } + + return acc; + }, []); +} \ No newline at end of file diff --git a/src/lib/storage.ts b/src/lib/storage/gcp.ts similarity index 97% rename from src/lib/storage.ts rename to src/lib/storage/gcp.ts index dbaa98fe..a8dd058a 100644 --- a/src/lib/storage.ts +++ b/src/lib/storage/gcp.ts @@ -3,7 +3,6 @@ import { Storage, StorageOptions } from "@google-cloud/storage"; interface GCPCredentials { type: string; project_id: string; - private_key_id: string; private_key: string; client_email: string; client_id: string; @@ -36,4 +35,4 @@ export async function uploadThumbnail(buffer: Buffer, pdfFilename: string) { const thumbFilename = pdfFilename.replace(".pdf", ".png"); await bucket.file(thumbFilename).save(buffer, { resumable: false, contentType: "image/png" }); return `https://storage.googleapis.com/${bucketName}/${thumbFilename}`; -} +} \ No newline at end of file diff --git a/src/lib/pdf.ts b/src/lib/storage/pdf.ts similarity index 96% rename from src/lib/pdf.ts rename to src/lib/storage/pdf.ts index 48c9f769..2904bbff 100644 --- a/src/lib/pdf.ts +++ b/src/lib/storage/pdf.ts @@ -16,5 +16,5 @@ export async function createPDFfromImages(files: File[]): Promise { page.drawImage(img, { x: 0, y: 0, width: img.width, height: img.height }); } - return pdfDoc.save(); + return pdfDoc.save(); } diff --git a/src/lib/utils.ts b/src/lib/utils/index.ts similarity index 98% rename from src/lib/utils.ts rename to src/lib/utils/index.ts index bd0c391d..a7dc3a13 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils/index.ts @@ -3,4 +3,4 @@ import { twMerge } from "tailwind-merge" export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) -} +} \ No newline at end of file diff --git a/src/lib/utils/paper-aggregation.ts b/src/lib/utils/paper-aggregation.ts new file mode 100644 index 00000000..7bab7540 --- /dev/null +++ b/src/lib/utils/paper-aggregation.ts @@ -0,0 +1,28 @@ +import { type IPaper } from "../../interface"; + + +export function extractUniqueValues(papers: IPaper[]) { + if (papers.length === 0) { + return { + unique_years: [], + unique_slots: [], + unique_exams: [], + unique_campuses: [], + unique_semesters: [], + }; + } + + const unique_years = Array.from(new Set(papers.map((paper) => paper.year))); + const unique_slots = Array.from(new Set(papers.map((paper) => paper.slot))); + const unique_exams = Array.from(new Set(papers.map((paper) => paper.exam))); + const unique_campuses = Array.from(new Set(papers.map((paper) => paper.campus))); + const unique_semesters = Array.from(new Set(papers.map((paper) => paper.semester))); + + return { + unique_years, + unique_slots, + unique_exams, + unique_campuses, + unique_semesters, + }; +} \ No newline at end of file diff --git a/src/lib/utils/regex.ts b/src/lib/utils/regex.ts new file mode 100644 index 00000000..23177109 --- /dev/null +++ b/src/lib/utils/regex.ts @@ -0,0 +1,3 @@ +export function escapeRegExp(text: string): string { + return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +} \ No newline at end of file diff --git a/src/lib/utils/slot-calculation.ts b/src/lib/utils/slot-calculation.ts new file mode 100644 index 00000000..284ef5ef --- /dev/null +++ b/src/lib/utils/slot-calculation.ts @@ -0,0 +1,10 @@ +export function calculateCorrespondingSlots(currentSlot: string): string[] { + const nextSlot = String.fromCharCode(currentSlot.charCodeAt(0) + 1); + + return [ + currentSlot + "1", + currentSlot + "2", + nextSlot + "1", + nextSlot + "2", + ]; +} \ No newline at end of file From a61bbe9702e521edcc95b9733fa2fc23d6b819d1 Mon Sep 17 00:00:00 2001 From: Abhinav Ganeshan Date: Thu, 2 Oct 2025 03:53:06 +0530 Subject: [PATCH 2/2] refactor: merge old utils folder with lib --- src/app/paper/[id]/page.tsx | 2 +- src/components/Card.tsx | 14 +++--- src/components/CatalogueContent.tsx | 2 +- src/components/Navbar.tsx | 31 +++++++------ src/components/PapersCarousel.tsx | 2 +- src/components/PinnedPapersCarousel.tsx | 2 +- src/components/UpcomingPaper.tsx | 12 +++--- src/components/pdfViewer.tsx | 2 +- src/components/ui/capsule.tsx | 28 ++++++++++++ src/lib/utils/array.ts | 7 +++ .../utils/download.ts} | 6 +-- src/{util/error.tsx => lib/utils/error.ts} | 4 +- src/lib/utils/string.ts | 24 +++++++++++ src/util/utils.tsx | 43 ------------------- 14 files changed, 100 insertions(+), 79 deletions(-) create mode 100644 src/components/ui/capsule.tsx create mode 100644 src/lib/utils/array.ts rename src/{util/download_paper.tsx => lib/utils/download.ts} (90%) rename src/{util/error.tsx => lib/utils/error.ts} (92%) create mode 100644 src/lib/utils/string.ts delete mode 100644 src/util/utils.tsx diff --git a/src/app/paper/[id]/page.tsx b/src/app/paper/[id]/page.tsx index 8161ccf2..fb720f77 100644 --- a/src/app/paper/[id]/page.tsx +++ b/src/app/paper/[id]/page.tsx @@ -3,7 +3,7 @@ import PdfViewer from "@/components/pdfViewer"; import RelatedPapers from "@/components/RelatedPaper"; import Loader from "@/components/ui/loader"; import { type ErrorResponse, type PaperResponse } from "@/interface"; -import { extractBracketContent } from "@/util/utils"; +import { extractBracketContent } from "@/lib/utils/string"; import axios, { type AxiosResponse } from "axios"; import { type Metadata } from "next"; import { redirect } from "next/navigation"; diff --git a/src/components/Card.tsx b/src/components/Card.tsx index 6a64f276..c32c1f99 100644 --- a/src/components/Card.tsx +++ b/src/components/Card.tsx @@ -5,15 +5,15 @@ import { type IPaper } from "@/interface"; import Image from "next/image"; import { Eye, Download, Check } from "lucide-react"; import { - capsule, extractBracketContent, extractWithoutBracketContent, -} from "@/util/utils"; +} from "@/lib/utils/string"; import { getSecureUrl, generateFileName, downloadFile, -} from "@/util/download_paper"; +} from "@/lib/utils/download"; +import { Capsule } from "@/components/ui/capsule"; import Link from "next/link"; import { cn } from "@/lib/utils"; @@ -88,10 +88,10 @@ const Card = ({ paper, onSelect, isSelected }: CardProps) => { {extractWithoutBracketContent(paper.subject)}
- {capsule(paper.exam)} - {capsule(paper.slot)} - {capsule(paper.year)} - {capsule(paper.semester)} + {paper.exam} + {paper.slot} + {paper.year} + {paper.semester}
diff --git a/src/components/CatalogueContent.tsx b/src/components/CatalogueContent.tsx index 327be364..90250139 100644 --- a/src/components/CatalogueContent.tsx +++ b/src/components/CatalogueContent.tsx @@ -20,7 +20,7 @@ import { getSecureUrl, generateFileName, downloadFile, -} from "@/util/download_paper"; +} from "@/lib/utils/download"; import type { ICourses } from "@/interface"; import JSZip from "jszip"; import { toast } from "react-hot-toast"; diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index a08ef34f..69104c66 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -48,7 +48,7 @@ function Navbar() {
- +
@@ -97,7 +97,7 @@ function Navbar() {