From 2f0fb2cb123dedb78455ad8dedec3f770b390f37 Mon Sep 17 00:00:00 2001 From: Gslmao Date: Thu, 11 Jun 2026 23:54:02 +0530 Subject: [PATCH 01/15] feat: define helpers for backend responses --- src/interface.ts | 7 +++++++ src/lib/utils/response.ts | 22 ++++++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 src/lib/utils/response.ts diff --git a/src/interface.ts b/src/interface.ts index 272ea56..4575a1b 100644 --- a/src/interface.ts +++ b/src/interface.ts @@ -52,6 +52,13 @@ export interface APIResponse { status: number; } +export interface ApiResponse { + status: "success" | "error"; + data: T | null; + message: string; + error?: any; +} + export interface ConverttoPDFResponse { url: string; secure_url: string; diff --git a/src/lib/utils/response.ts b/src/lib/utils/response.ts new file mode 100644 index 0000000..b13c0e8 --- /dev/null +++ b/src/lib/utils/response.ts @@ -0,0 +1,22 @@ +import { NextResponse } from "next/server" +import { ApiResponse } from '@/interface' + +export const success = ( + data: T, + message = "OK", + status = 200 +) => + NextResponse.json>( + { status: "success", data, message }, + { status } + ); + +export const failure = ( + message: string, + status = 400, + error?: any +) => + NextResponse.json>( + { status: "error", data: null, message, error }, + { status } + ) \ No newline at end of file From c10c3ff419f156d3ded49cf8a4e403970f330019 Mon Sep 17 00:00:00 2001 From: Gslmao Date: Thu, 11 Jun 2026 23:56:49 +0530 Subject: [PATCH 02/15] feat: implement helper functions in course-list, papers/count endpoints and update the callers --- src/app/api/course-list/route.ts | 9 +++------ src/app/api/papers/count/route.ts | 9 +++------ src/context/courseContext.tsx | 14 +++++++------- 3 files changed, 13 insertions(+), 19 deletions(-) diff --git a/src/app/api/course-list/route.ts b/src/app/api/course-list/route.ts index 47bf19d..c5d03b3 100644 --- a/src/app/api/course-list/route.ts +++ b/src/app/api/course-list/route.ts @@ -1,17 +1,14 @@ -import { NextResponse } from "next/server"; import { getCourseList } from "@/lib/services/subject"; +import { success, failure } from "@/lib/utils/response"; export const dynamic = "force-dynamic"; export async function GET() { try { const courses = await getCourseList(); - return NextResponse.json(courses, { status: 200 }); + return success(courses); } catch (error) { console.error(error); - return NextResponse.json( - { message: "Failed to fetch courses", error }, - { status: 500 }, - ); + return failure("Failed to fetch courses", 500, error); } } diff --git a/src/app/api/papers/count/route.ts b/src/app/api/papers/count/route.ts index cc2fbf1..bd7266a 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 { getCourseCounts } from "@/lib/services/paper"; +import { success, failure } from "@/lib/utils/response"; export const dynamic = "force-dynamic"; @@ -7,11 +7,8 @@ export async function GET(req: Request) { try { const courseCount = await getCourseCounts(); - return NextResponse.json(courseCount, { status: 200 }); + return success(courseCount); } catch (error) { - return NextResponse.json( - { message: "Failed to fetch course counts", error }, - { status: 500 }, - ); + return failure("Failed to fetch course counts", 500, error); } } diff --git a/src/context/courseContext.tsx b/src/context/courseContext.tsx index aef5386..8c873b3 100644 --- a/src/context/courseContext.tsx +++ b/src/context/courseContext.tsx @@ -8,6 +8,8 @@ import { type ICourseWithCount, } from "@/interface"; +import { type ApiResponse } from '@/interface' + interface CoursesContextType { courses: ICourseWithCount[]; loading: boolean; @@ -30,19 +32,17 @@ export function CoursesProvider({ children }: { children: React.ReactNode }) { setLoading(true); setError(null); try { - const res = await axios.get("/api/course-list", { + const res = await axios.get>("/api/course-list", { headers: { "Cache-Control": "no-cache" }, }); - - const countRes = await axios.get("/api/papers/count", { + const countRes = await axios.get>("/api/papers/count", { headers: { "Cache-Control": "no-cache" }, }); - const countMap = new Map( - countRes.data.map((c) => [c.name, c.count]), + (countRes.data.data ?? []).map((c) => [c.name, c.count]), ); - const mergedCourses: ICourseWithCount[] = res.data.map((course) => ({ + const mergedCourses: ICourseWithCount[] = (res.data.data ?? []).map((course) => ({ _id: course._id, name: course.name, count: countMap.get(course.name) ?? 0, @@ -50,7 +50,7 @@ export function CoursesProvider({ children }: { children: React.ReactNode }) { setCourses(mergedCourses); } catch (err: unknown) { - if (axios.isAxiosError(err)) { + if (axios.isAxiosError>(err)) { setError(err.response?.data?.message ?? err.message); } else if (err instanceof Error) { setError(err.message); From c5f26da9af7b992b254cd7ffc80ab22e01802305 Mon Sep 17 00:00:00 2001 From: Gslmao Date: Fri, 12 Jun 2026 00:00:30 +0530 Subject: [PATCH 03/15] implement: helpers for papers, related-subject endpoints --- src/app/api/papers/route.ts | 15 ++++-------- src/app/api/related-subject/route.ts | 18 ++++----------- src/components/CatalogueContent.tsx | 34 ++++++++++++++-------------- src/components/RelatedPaper.tsx | 12 ++++++---- 4 files changed, 35 insertions(+), 44 deletions(-) diff --git a/src/app/api/papers/route.ts b/src/app/api/papers/route.ts index b7840dd..870202a 100644 --- a/src/app/api/papers/route.ts +++ b/src/app/api/papers/route.ts @@ -1,5 +1,6 @@ -import { NextResponse, type NextRequest } from "next/server"; +import { type NextRequest } from "next/server"; import { getPapersBySubject } from "@/lib/services/paper"; +import { success, failure } from "@/lib/utils/response"; export const dynamic = "force-dynamic"; @@ -8,18 +9,12 @@ export async function GET(req: NextRequest) { const url = req.nextUrl.searchParams; const sub = url.get("subject"); if (!sub) { - return NextResponse.json( - { message: "Subject query parameter is required" }, - { status: 400 }, - ); + return failure("Subject query parameter is required", 400); } const paper = await getPapersBySubject(sub); - return NextResponse.json(paper, { status: 200 }); + return success(paper); } catch (error) { - return NextResponse.json( - { message: "Failed to fetch papers", error }, - { status: 500 }, - ); + return failure("Failed to fetch papers", 500, error); } } diff --git a/src/app/api/related-subject/route.ts b/src/app/api/related-subject/route.ts index 6f374ff..ab2852a 100644 --- a/src/app/api/related-subject/route.ts +++ b/src/app/api/related-subject/route.ts @@ -1,5 +1,6 @@ import { getRelatedSubjects } from "@/lib/services/subject"; -import { NextResponse, type NextRequest } from "next/server"; +import { type NextRequest } from "next/server"; +import { success, failure } from "@/lib/utils/response"; export const dynamic = "force-dynamic"; @@ -9,21 +10,12 @@ export async function GET(req: NextRequest) { const subject = url.get("subject"); if (!subject) { - return NextResponse.json( - { message: "Subject query parameter is required" }, - { status: 400 }, - ); + return failure("Subject query parameter is required", 400); } const relatedSubjects = await getRelatedSubjects(subject); - return NextResponse.json( - {related_subjects: relatedSubjects}, - { status: 200 }, - ); + return success({ related_subjects: relatedSubjects }); } catch (error) { - return NextResponse.json( - { message: "Failed to fetch related subject", error }, - { status: 500 }, - ); + return failure("Failed to fetch related subject", 500, error); } } \ No newline at end of file diff --git a/src/components/CatalogueContent.tsx b/src/components/CatalogueContent.tsx index df84ff6..f99d245 100644 --- a/src/components/CatalogueContent.tsx +++ b/src/components/CatalogueContent.tsx @@ -4,7 +4,7 @@ import { useSearchParams } from "next/navigation"; import { useEffect, useState } from "react"; import axios, { type AxiosError } from "axios"; import { Button } from "@/components/ui/button"; -import { type IPaper, type Filters, type StoredSubjects } from "@/interface"; +import { type IPaper, type Filters, type StoredSubjects, type ApiResponse } from "@/interface"; import Card from "./Card"; import Loader from "./ui/loader"; import SideBar from "../components/SideBar"; @@ -71,13 +71,10 @@ const CatalogueContentInner = ({ subject }: { subject: string | null }) => { if (!subject) return; const fetchRelatedSubjects = async () => { try { - const res = await axios.get<{ related_subjects: string[] }>( - "/api/related-subject", - { - params: { subject }, - }, - ); - const data = res.data.related_subjects; + const res = await axios.get>("/api/related-subject", { + params: { subject }, + }); + const data = res.data.data?.related_subjects; if (data && data.length > 0) { setRelatedSubjects(data); } else { @@ -153,20 +150,23 @@ const CatalogueContentInner = ({ subject }: { subject: string | null }) => { const fetchPapers = async () => { setLoading(true); try { - const papersResponse = await axios.get("/api/papers", { - params: { subject }, - }); - const data: Filters = papersResponse.data; - const papersData = data.papers; + const papersResponse = await axios.get>( + "/api/papers", + { + params: { subject }, + }, + ); + + const data = papersResponse.data.data!; + const papersData = data?.papers ?? []; setFilterOptions(data); setPapers(papersData); } catch (error) { setPapers([]); - const axiosError = error as AxiosError; + setError( - axios.isAxiosError(axiosError) - ? ((axiosError.response?.data as { message?: string })?.message ?? - "Error fetching papers") + axios.isAxiosError>(error) + ? (error.response?.data.message ?? "Error fetching papers") : "Error fetching papers", ); } finally { diff --git a/src/components/RelatedPaper.tsx b/src/components/RelatedPaper.tsx index 447be7c..c428341 100644 --- a/src/components/RelatedPaper.tsx +++ b/src/components/RelatedPaper.tsx @@ -8,6 +8,7 @@ import { type IPaper, type Filters } from "@/interface"; import Card from "@/components/Card"; import Loader from "@/components/ui/loader"; import { Button } from "./ui/button"; +import { type ApiResponse } from "@/interface" const RelatedPapers = () => { const params = useParams(); @@ -24,11 +25,14 @@ const RelatedPapers = () => { const paper = getpaper.data; setCurrentPaper(paper); - const allPapersBySubject = await axios.get("/api/papers", { - params: { subject: paper.subject }, - }); + const allPapersBySubject = await axios.get>( + "/api/papers", + { + params: { subject: paper.subject }, + }, + ); - const all = allPapersBySubject.data.papers; + const all = allPapersBySubject.data.data?.papers ?? []; const sameExam = all .filter((p) => p._id !== paper._id && p.exam === paper.exam) From e079e5b59bab45676f9593822c56ccd1e4433137 Mon Sep 17 00:00:00 2001 From: Gslmao Date: Fri, 12 Jun 2026 00:01:19 +0530 Subject: [PATCH 04/15] implement: helpers for report-tag endpoint --- src/app/api/report-tag/route.ts | 12 +++--------- src/components/ReportTagModal.tsx | 3 ++- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/app/api/report-tag/route.ts b/src/app/api/report-tag/route.ts index 032cbd1..d709e18 100644 --- a/src/app/api/report-tag/route.ts +++ b/src/app/api/report-tag/route.ts @@ -1,6 +1,6 @@ -import { NextResponse } from "next/server"; import { reportTag, ReportTagBody } from "@/lib/services/report"; import { rateLimitCheck } from "@/lib/utils/rate-limiter"; +import { success, failure } from "@/lib/utils/response"; import { customErrorHandler } from "@/lib/utils/error"; export async function POST(req: Request & { ip?: string }) { @@ -9,18 +9,12 @@ export async function POST(req: Request & { ip?: string }) { const paperId = typeof body.paperId === "string" ? body.paperId : undefined; if (!paperId) { - return NextResponse.json( - { error: "paperId is required" }, - { status: 400 } - ); + return failure("paperId is required", 400); } await rateLimitCheck(req, paperId); const newReport = await reportTag(paperId, body); - return NextResponse.json( - { message: "Report submitted.", report: newReport }, - { status: 201 } - ); + return success({ message: "Report submitted.", report: newReport }, "Created", 201); } catch (err) { console.error(err); return customErrorHandler(err, "Failed to submit tag report."); diff --git a/src/components/ReportTagModal.tsx b/src/components/ReportTagModal.tsx index 91c31e2..a04e694 100644 --- a/src/components/ReportTagModal.tsx +++ b/src/components/ReportTagModal.tsx @@ -16,8 +16,9 @@ import LabeledInput from "@/components/ui/LabeledInput"; import LabeledSelect from "@/components/ui/LabeledSelect"; import axios, { AxiosError } from "axios"; import toast from "react-hot-toast"; +import { type ApiResponse } from '@/interface' -type ReportResponse = { error?: string; message?: string }; +type ReportResponse = ApiResponse<{ error?: string; message?: string }>; interface ReportTagModalProps { paperId: string; From 78039bd807831c256e887050b0a0f42590f673f4 Mon Sep 17 00:00:00 2001 From: Gslmao Date: Fri, 12 Jun 2026 00:03:05 +0530 Subject: [PATCH 05/15] implement: helpers for request, upcoming-papers endpoints --- src/app/api/request/route.ts | 17 ++++------------- src/app/api/upcoming-papers/route.ts | 20 ++++---------------- src/app/request/page.tsx | 7 +++---- src/components/ui/RequestModal.tsx | 14 +++++++------- 4 files changed, 18 insertions(+), 40 deletions(-) diff --git a/src/app/api/request/route.ts b/src/app/api/request/route.ts index 48c9302..e6dce2d 100644 --- a/src/app/api/request/route.ts +++ b/src/app/api/request/route.ts @@ -1,6 +1,6 @@ -import { NextResponse } from "next/server"; import { connectToDatabase } from "@/lib/database/mongoose"; import PaperRequest from "@/db/paperRequest"; +import { success, failure } from "@/lib/utils/response"; export async function POST(req: Request) { try { @@ -15,22 +15,13 @@ export async function POST(req: Request) { const { subject, exam, slot, year } = body; if (!subject || !exam || !slot || !year) { - return NextResponse.json( - { error: "All fields are required." }, - { status: 400 }, - ); + return failure("All fields are required.", 400); } const newRequest = await PaperRequest.create({ subject, exam, slot, year }); - return NextResponse.json( - { message: "Paper request submitted successfully!", request: newRequest }, - { status: 201 }, - ); + return success({ message: "Paper request submitted successfully!", request: newRequest }, "Created", 201); } catch (error) { console.error("Error creating paper request:", error); - return NextResponse.json( - { error: "Failed to submit request." }, - { status: 500 }, - ); + return failure("Failed to submit request.", 500, error); } } diff --git a/src/app/api/upcoming-papers/route.ts b/src/app/api/upcoming-papers/route.ts index 9c76428..f5b1f7a 100644 --- a/src/app/api/upcoming-papers/route.ts +++ b/src/app/api/upcoming-papers/route.ts @@ -1,6 +1,6 @@ -import { NextResponse } from "next/server"; import { connectToDatabase } from "@/lib/database/mongoose"; import UpcomingSubject from "@/db/upcoming-paper"; +import { success, failure } from "@/lib/utils/response"; export const dynamic = "force-dynamic"; @@ -13,24 +13,12 @@ export async function GET() { .lean(); if (selectedSubjects.length === 0) { - return NextResponse.json( - { - message: "No selected papers found.", - }, - { status: 404 }, - ); + return failure("No selected papers found.", 404); } - return NextResponse.json(selectedSubjects, { - status: 200, - }); + return success(selectedSubjects); } catch (error) { console.error("Error fetching papers:", error); - return NextResponse.json( - { - error: "Failed to fetch papers.", - }, - { status: 500 }, - ); + return failure("Failed to fetch papers.", 500, error); } } diff --git a/src/app/request/page.tsx b/src/app/request/page.tsx index 97367f6..264370c 100644 --- a/src/app/request/page.tsx +++ b/src/app/request/page.tsx @@ -20,6 +20,7 @@ import toast from "react-hot-toast"; import { Search } from "lucide-react"; import SkeletonPaperCard from "@/components/SkeletonPaperCard"; import { useCourses } from "@/context/courseContext"; +import { type ApiResponse } from '@/interface' type Course = { name?: string | null; @@ -48,11 +49,9 @@ export default function PaperRequest() { async function fetchPapers() { try { setIsLoading(true); - const response = await axios.get( - "/api/upcoming-papers", - ); + const response = await axios.get>("/api/upcoming-papers"); - setDisplayPapers(response.data); + setDisplayPapers(response.data.data ?? []); } catch (error) { console.error("Failed to fetch papers:", error); } finally { diff --git a/src/components/ui/RequestModal.tsx b/src/components/ui/RequestModal.tsx index 23339e8..c2dfa92 100644 --- a/src/components/ui/RequestModal.tsx +++ b/src/components/ui/RequestModal.tsx @@ -78,15 +78,15 @@ const RequestModal = ({section = "navbar"} : {section? : string}) => { try { await toast.promise( axios.post("/api/request", { - subject: selectedSubject, - exam: selectedExam, - slot: selectedSlot, - year: selectedYear, + subject: selectedSubject, + exam: selectedExam, + slot: selectedSlot, + year: selectedYear, }), { - loading: "Submitting your request...", - success: "Your paper request was submitted successfully", - error: "Failed to submit your request. Please try again later.", + loading: "Submitting your request...", + success: "Your paper request was submitted successfully", + error: "Failed to submit your request. Please try again later.", }, ); From aadd2791956929c939fdd5d098b0ffda12044786 Mon Sep 17 00:00:00 2001 From: Gslmao Date: Fri, 12 Jun 2026 00:03:46 +0530 Subject: [PATCH 06/15] implement: helpers for selected-papers endpoint --- src/app/api/selected-papers/route.ts | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/src/app/api/selected-papers/route.ts b/src/app/api/selected-papers/route.ts index 5c822f6..1796356 100644 --- a/src/app/api/selected-papers/route.ts +++ b/src/app/api/selected-papers/route.ts @@ -1,6 +1,6 @@ -import { NextResponse } from "next/server"; import { connectToDatabase } from "@/lib/database/mongoose"; import Paper from "@/db/papers"; +import { success, failure } from "@/lib/utils/response"; export const dynamic = "force-dynamic"; @@ -11,23 +11,11 @@ export async function GET() { const selectedPapers = await Paper.find({ isSelected: true }).limit(8); if (selectedPapers.length === 0) { - return NextResponse.json( - { - message: "No selected papers found.", - }, - { status: 404 }, - ); + return failure("No selected papers found.", 404); } - return NextResponse.json(selectedPapers, { - status: 200, - }); + return success(selectedPapers); } catch (error) { console.error("Error fetching papers:", error); - return NextResponse.json( - { - error: "Failed to fetch papers.", - }, - { status: 500 }, - ); + return failure("Failed to fetch papers.", 500, error); } } From e07ceb9427d76c3ccc0288fcc55aa379ec8d9833 Mon Sep 17 00:00:00 2001 From: Gslmao Date: Fri, 12 Jun 2026 00:05:34 +0530 Subject: [PATCH 07/15] implement: helpers for subscribe endpoint --- src/app/api/subscribe/route.ts | 10 +++++----- src/components/Footer.tsx | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/app/api/subscribe/route.ts b/src/app/api/subscribe/route.ts index ce22818..791e7d3 100644 --- a/src/app/api/subscribe/route.ts +++ b/src/app/api/subscribe/route.ts @@ -1,19 +1,19 @@ -import { NextResponse } from "next/server"; -import { customErrorHandler } from "@/lib/utils/error"; import { subscribeEmail } from "@/lib/services/subscribe"; +import { success, failure } from "@/lib/utils/response"; +import { customErrorHandler } from "@/lib/utils/error"; export async function POST(req: Request) { try { const { email } = (await req.json()) as { email: string }; if (!email) { - return NextResponse.json({ error: "Email is required" }, { status: 400 }); + return failure("Email is required", 400); } await subscribeEmail(email); - return NextResponse.json({ message: "Email added successfully" }); + return success({ message: "Email added successfully" }); } catch (error) { console.error("Error adding email:", error); return customErrorHandler(error, "Failed to add email"); } -} \ No newline at end of file +} \ No newline at end of file diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx index c59b7b4..679d165 100644 --- a/src/components/Footer.tsx +++ b/src/components/Footer.tsx @@ -43,7 +43,7 @@ export default function Footer() { body: JSON.stringify({ email }), }) .then(async (res) => { - const data = (await res.json()) as SubscribeResponse; + const data = (await res.json()).data as SubscribeResponse; if (!res.ok) throw new Error(data.message ?? "Something went wrong."); return data; }), From 3bac05218795a4252fe2e8be6915437531c6ad7f Mon Sep 17 00:00:00 2001 From: Gslmao Date: Fri, 12 Jun 2026 00:05:52 +0530 Subject: [PATCH 08/15] implement: helpers for upload endpoint --- src/app/api/upload/route.ts | 22 +++++----------------- src/app/upload/page.tsx | 9 +++++++-- 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/src/app/api/upload/route.ts b/src/app/api/upload/route.ts index 2fad98e..e5264ba 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/database/mongoose"; import { PaperAdmin } from "@/db/papers"; import { createPDFfromImages } from "@/lib/storage/pdf"; import { uploadPDF, uploadThumbnail } from "@/lib/storage/gcp"; +import { success, failure } from "@/lib/utils/response"; export const runtime = "nodejs"; @@ -15,19 +15,13 @@ export async function POST(req: Request) { const thumb = formData.get("thumbnail") as File | null; if (files.length === 0) { - return NextResponse.json( - { error: "No files received." }, - { status: 400 }, - ); + return failure("No files received.", 400); } let pdfBytes: Uint8Array; if (isPdf) { if (!files[0]) { - return NextResponse.json( - { error: "No PDF file provided." }, - { status: 400 }, - ); + return failure("No PDF file provided.", 400); } pdfBytes = new Uint8Array(await files[0].arrayBuffer()); } else { @@ -57,15 +51,9 @@ export async function POST(req: Request) { }); await paper.save(); - return NextResponse.json( - { status: "success", file_url, thumbnail_url }, - { status: 201 }, - ); + return success({ file_url, thumbnail_url }, "Created", 201); } catch (error) { console.error(error); - return NextResponse.json( - { message: "Failed to upload papers", error }, - { status: 500 }, - ); + return failure("Failed to upload papers", 500, error); } } diff --git a/src/app/upload/page.tsx b/src/app/upload/page.tsx index 18533bb..1a00b3b 100644 --- a/src/app/upload/page.tsx +++ b/src/app/upload/page.tsx @@ -25,6 +25,7 @@ import { CSS } from "@dnd-kit/utilities"; import Dropzone from "react-dropzone"; import { Upload, XIcon } from "lucide-react"; import { getDocument, GlobalWorkerOptions } from "pdfjs-dist"; +import { type ApiResponse } from "@/interface" GlobalWorkerOptions.workerSrc = "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.8.69/pdf.worker.min.mjs"; @@ -34,6 +35,10 @@ interface APIResponse { message?: string; } +interface uploadResponse { + file_url: string; + thumbnail_url: string; +} export default function Page() { const campus = "Vellore"; const [files, setFiles] = useState([]); @@ -273,11 +278,11 @@ export default function Page() { await toast.promise( async () => { try { - await axios.post("/api/upload", formData); + await axios.post>("/api/upload", formData); return { message: "Papers uploaded successfully!" }; } catch (error) { if (error instanceof AxiosError && error.response?.data) { - const errorData = error.response.data as APIResponse; + const errorData = error.response.data; const errorMessage = errorData.message ?? "Failed to upload papers"; throw new Error(errorMessage); From 991491e89e94ff89ce918149bfc6be4c9a32a2b5 Mon Sep 17 00:00:00 2001 From: Gslmao Date: Fri, 12 Jun 2026 00:10:57 +0530 Subject: [PATCH 09/15] implement: helpers for user-papers endpoint --- src/app/api/user-papers/route.ts | 13 +++---------- src/components/PinnedPapersCarousel.tsx | 16 ++++++++++------ src/components/ui/PinnedModal.tsx | 12 ++++++++++-- 3 files changed, 23 insertions(+), 18 deletions(-) diff --git a/src/app/api/user-papers/route.ts b/src/app/api/user-papers/route.ts index 8d2b9df..3c47b45 100644 --- a/src/app/api/user-papers/route.ts +++ b/src/app/api/user-papers/route.ts @@ -1,8 +1,8 @@ -import { NextResponse } from "next/server"; import { connectToDatabase } from "@/lib/database/mongoose"; import Paper from "@/db/papers"; import { StoredSubjects } from "@/interface"; import { transformPapersToSubjectSlots } from "@/lib/services/paper-transform"; +import { success, failure } from "@/lib/utils/response"; export const dynamic = "force-dynamic"; @@ -19,16 +19,9 @@ export async function POST(req: Request) { const transformedPapers = transformPapersToSubjectSlots(usersPapers); - return NextResponse.json(transformedPapers, { - status: 200, - }); + return success(transformedPapers); } catch (error) { console.error("Error fetching papers:", error); - return NextResponse.json( - { - error: "Failed to fetch papers.", - }, - { status: 500 }, - ); + return failure("Failed to fetch papers.", 500, error); } } diff --git a/src/components/PinnedPapersCarousel.tsx b/src/components/PinnedPapersCarousel.tsx index ec90d28..be9f72e 100644 --- a/src/components/PinnedPapersCarousel.tsx +++ b/src/components/PinnedPapersCarousel.tsx @@ -16,6 +16,12 @@ import { chunkArray } from "@/lib/utils/array"; import { type StoredSubjects } from "@/interface"; import SkeletonPaperCard from "./SkeletonPaperCard"; import PinnedModal from "./ui/PinnedModal"; +import { type ApiResponse } from "@/interface" + +interface userPaperResponse { + subject: string; + slots: string[] +} function PinnedPapersCarousel() { const [isLoading, setIsLoading] = useState(true); @@ -61,12 +67,12 @@ function PinnedPapersCarousel() { localStorage.getItem("userSubjects") ?? "[]", ) as StoredSubjects; - const response = await axios.post<{ subject: string; slots: string[] }[]>( + const response = await axios.post>( "/api/user-papers", storedSubjects, ); - const fetchedPapers = response.data; + const fetchedPapers = response.data.data!; const fetchedSubjectsSet = new Set( fetchedPapers.map((paper) => paper.subject), @@ -114,11 +120,9 @@ function PinnedPapersCarousel() { localStorage.getItem("userSubjects") ?? "[]", ) as StoredSubjects; - const response = await axios.post< - { subject: string; slots: string[] }[] - >("/api/user-papers", storedSubjects); + const response = await axios.post>("/api/user-papers", storedSubjects); - const fetchedPapers = response.data; + const fetchedPapers = response.data.data!; const fetchedSubjectsSet = new Set( fetchedPapers.map((paper) => paper.subject), diff --git a/src/components/ui/PinnedModal.tsx b/src/components/ui/PinnedModal.tsx index b977941..a341965 100644 --- a/src/components/ui/PinnedModal.tsx +++ b/src/components/ui/PinnedModal.tsx @@ -27,8 +27,16 @@ import { verticalListSortingStrategy, arrayMove, } from "@dnd-kit/sortable"; + +import { type ApiResponse } from '@/interface' + import { CSS } from "@dnd-kit/utilities"; +interface userPaperResponse { + subject: string; + slots: string[] +} + const SortableItem = ({ id, subject, @@ -97,11 +105,11 @@ const PinnedModal = ({ const storedSubjects = JSON.parse( localStorage.getItem("userSubjects") ?? "[]", ) as StoredSubjects; - const response = await axios.post<{ subject: string; slots: string[] }[]>( + const response = await axios.post>( "/api/user-papers", storedSubjects, ); - const fetchedPapers = response.data; + const fetchedPapers = response.data.data!; const fetchedSubjectsSet = new Set( fetchedPapers.map((paper) => paper.subject), ); From fe4ee845a8d5ea88ce6bd3f8f964358f41cdee30 Mon Sep 17 00:00:00 2001 From: Gslmao Date: Fri, 12 Jun 2026 00:16:44 +0530 Subject: [PATCH 10/15] update frontend callers to match api shape --- src/app/actions/get-papers-by-id.ts | 15 +++++++++------ src/components/PapersCarousel.tsx | 7 +++---- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/app/actions/get-papers-by-id.ts b/src/app/actions/get-papers-by-id.ts index 3f30051..3cbbcb6 100644 --- a/src/app/actions/get-papers-by-id.ts +++ b/src/app/actions/get-papers-by-id.ts @@ -1,20 +1,23 @@ -import { type PaperResponse } from "@/interface"; +import { type PaperResponse, ApiResponse } from "@/interface"; import axios, { type AxiosResponse } from "axios"; export const fetchPaperID = async (id: string): Promise => { const serverUrl = process.env.SERVER_URL ?? "https://papers.codechefvit.com"; try { - const response: AxiosResponse = await axios.get( + const response: AxiosResponse> = await axios.get( `${serverUrl}/api/paper-by-id/${id}`, ); - return response.data; + + if (!response.data.data) { + throw new Error("Paper not found"); + } + return response.data.data; } catch (err: unknown) { if (axios.isAxiosError(err)) { - console.error("Axios error:", err.response?.data ?? err.message); + console.error("Axios error:", err.response?.data.message ?? err.message); const errorMessage = - (err.response?.data as { message?: string })?.message ?? - "Failed to fetch paper"; + (err.response?.data.message ?? "Failed to fetch paper"); throw new Error(errorMessage); } else { console.error("Unexpected error:", err); diff --git a/src/components/PapersCarousel.tsx b/src/components/PapersCarousel.tsx index 3fd4a01..b2368c5 100644 --- a/src/components/PapersCarousel.tsx +++ b/src/components/PapersCarousel.tsx @@ -15,6 +15,7 @@ import Autoplay from "embla-carousel-autoplay"; import { chunkArray } from "@/lib/utils/array"; import { Skeleton } from "@/components/ui/skeleton"; import SkeletonPaperCard from "@/components/SkeletonPaperCard"; +import { type ApiResponse } from "@/interface" function PapersCarousel() { const [displayPapers, setDisplayPapers] = useState([]); @@ -41,10 +42,8 @@ function PapersCarousel() { const fetchPapers = async () => { try { setIsLoading(true); - const response = await axios.get( - "/api/upcoming-papers", - ); - setDisplayPapers(response.data); + const response = await axios.get>("/api/upcoming-papers"); + setDisplayPapers(response.data.data ?? []); } catch (error) { console.error("Failed to fetch papers:", error); } finally { From c151ac0ae94351fdf1326cabd61e92643818740f Mon Sep 17 00:00:00 2001 From: Gslmao Date: Fri, 12 Jun 2026 00:18:04 +0530 Subject: [PATCH 11/15] update error handler to comply with api response --- src/lib/utils/error.ts | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/lib/utils/error.ts b/src/lib/utils/error.ts index f4b1890..7340d70 100644 --- a/src/lib/utils/error.ts +++ b/src/lib/utils/error.ts @@ -1,4 +1,4 @@ -import { NextResponse } from "next/server"; +import { failure } from "@/lib/utils/response" export class CustomError extends Error { status: number; @@ -12,14 +12,8 @@ export class CustomError extends Error { export function customErrorHandler(error: unknown, defaultMessage: string) { if (error instanceof CustomError) { - return NextResponse.json( - {message: error.message}, - {status: error.status} - ); + return failure(error.message, error.status); } else { - return NextResponse.json( - {message: defaultMessage}, - {status: 500} - ); + return failure(defaultMessage, 500) } } From fc1a0d4b393e8cce7e3dc184eb1f8066f8c457a9 Mon Sep 17 00:00:00 2001 From: Gslmao Date: Sat, 13 Jun 2026 11:44:41 +0530 Subject: [PATCH 12/15] impl: helper for paper-by-is endpoint and update callers --- src/app/api/paper-by-id/[id]/route.ts | 14 +++++--------- src/components/RelatedPaper.tsx | 7 +++++-- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/app/api/paper-by-id/[id]/route.ts b/src/app/api/paper-by-id/[id]/route.ts index 5a69c28..96e75e9 100644 --- a/src/app/api/paper-by-id/[id]/route.ts +++ b/src/app/api/paper-by-id/[id]/route.ts @@ -1,23 +1,19 @@ -import { NextResponse } from "next/server"; import { Types } from "mongoose"; import { getPaperById } from "@/lib/services/paper"; +import { success, failure } from "@/lib/utils/response" export async function GET(req: Request, { params }: { params: { id: string } }) { try { const { id } = params; if (!Types.ObjectId.isValid(id)) { - return NextResponse.json({ message: "Invalid paper ID" }, { status: 400 }); + return failure("Invalid paper ID"); } - const paper = await getPaperById(id); - - return NextResponse.json(paper, { status: 200 }); + + return success(paper); } catch (error) { console.error(error); - return NextResponse.json( - { message: "Failed to fetch paper", error }, - { status: 500 }, - ); + return failure("Failed to fetch paper", 500, error); } } diff --git a/src/components/RelatedPaper.tsx b/src/components/RelatedPaper.tsx index c428341..7085be8 100644 --- a/src/components/RelatedPaper.tsx +++ b/src/components/RelatedPaper.tsx @@ -21,8 +21,11 @@ const RelatedPapers = () => { useEffect(() => { const fetchData = async () => { try { - const getpaper = await axios.get(`/api/paper-by-id/${id}`); - const paper = getpaper.data; + const getpaper = await axios.get>(`/api/paper-by-id/${id}`); + if (!getpaper.data.data) { + throw new Error("Paper not found"); + } + const paper = getpaper.data.data; setCurrentPaper(paper); const allPapersBySubject = await axios.get>( From 5e4369f985b18d15fee01e910e7aaf177bde88ec Mon Sep 17 00:00:00 2001 From: Gslmao Date: Sat, 13 Jun 2026 12:27:02 +0530 Subject: [PATCH 13/15] update middleware to comply with response shape --- src/middleware.ts | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/middleware.ts b/src/middleware.ts index 6e5fa64..42cd036 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -4,6 +4,7 @@ import { paperRequestRatelimit, subscribeRatelimit, } from "./lib/utils/ratelimit"; +import { failure } from "@/lib/utils/response" export const config = { matcher: ["/api/upload", "/api/request", "/api/subscribe"], @@ -16,30 +17,21 @@ export default async function middleware(request: NextRequest) { if (pathname === "/api/upload") { const { success } = await aiUploadRatelimit.limit(ip); if (!success) { - return NextResponse.json( - { message: "You can upload a maximum of 5 papers every 15 minutes" }, - { status: 429 }, - ); + return failure("You can upload a maximum of 5 papers every 15 minutes", 429); } } if (pathname === "/api/request") { const { success } = await paperRequestRatelimit.limit(ip); if (!success) { - return NextResponse.json( - { message: "You can submit a maximum of 5 requests every 15 minutes" }, - { status: 429 }, - ); + return failure("You can submit a maximum of 5 requests every 15 minutes", 429); } } if (pathname === "/api/subscribe") { const { success } = await subscribeRatelimit.limit(ip); if (!success) { - return NextResponse.json( - { message: "Maximum of 3 newsletter subscriptions per hour" }, - { status: 429 }, - ); + return failure("Maximum of 3 newsletter subscriptions per hour", 429); } } From 1f0ff3cfb808a455ed184c8b5d20d2991f520ba2 Mon Sep 17 00:00:00 2001 From: Gslmao Date: Sun, 14 Jun 2026 20:31:52 +0530 Subject: [PATCH 14/15] refactor: backend doesent send error objects to frontend --- 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 | 3 ++- src/app/api/papers/route.ts | 5 +++-- src/app/api/related-subject/route.ts | 3 ++- src/app/api/request/route.ts | 2 +- src/app/api/selected-papers/route.ts | 2 +- src/app/api/upcoming-papers/route.ts | 2 +- src/app/api/upload/route.ts | 2 +- src/app/api/user-papers/route.ts | 2 +- src/interface.ts | 1 - src/lib/utils/response.ts | 3 +-- 12 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/app/api/course-list/route.ts b/src/app/api/course-list/route.ts index c5d03b3..f84f51f 100644 --- a/src/app/api/course-list/route.ts +++ b/src/app/api/course-list/route.ts @@ -9,6 +9,6 @@ export async function GET() { return success(courses); } catch (error) { console.error(error); - return failure("Failed to fetch courses", 500, error); + return failure("Failed to fetch courses", 500); } } diff --git a/src/app/api/paper-by-id/[id]/route.ts b/src/app/api/paper-by-id/[id]/route.ts index 96e75e9..243754e 100644 --- a/src/app/api/paper-by-id/[id]/route.ts +++ b/src/app/api/paper-by-id/[id]/route.ts @@ -14,6 +14,6 @@ export async function GET(req: Request, { params }: { params: { id: string } }) return success(paper); } catch (error) { console.error(error); - return failure("Failed to fetch paper", 500, error); + return failure("Failed to fetch paper", 500); } } diff --git a/src/app/api/papers/count/route.ts b/src/app/api/papers/count/route.ts index bd7266a..bdddfea 100644 --- a/src/app/api/papers/count/route.ts +++ b/src/app/api/papers/count/route.ts @@ -9,6 +9,7 @@ export async function GET(req: Request) { return success(courseCount); } catch (error) { - return failure("Failed to fetch course counts", 500, error); + console.error(error); + return failure("Failed to fetch course counts", 500); } } diff --git a/src/app/api/papers/route.ts b/src/app/api/papers/route.ts index 870202a..a5eb23c 100644 --- a/src/app/api/papers/route.ts +++ b/src/app/api/papers/route.ts @@ -9,12 +9,13 @@ export async function GET(req: NextRequest) { const url = req.nextUrl.searchParams; const sub = url.get("subject"); if (!sub) { - return failure("Subject query parameter is required", 400); + return failure("Subject query parameter is required"); } const paper = await getPapersBySubject(sub); return success(paper); } catch (error) { - return failure("Failed to fetch papers", 500, error); + console.error(error); + return failure("Failed to fetch papers", 500); } } diff --git a/src/app/api/related-subject/route.ts b/src/app/api/related-subject/route.ts index ab2852a..debd00d 100644 --- a/src/app/api/related-subject/route.ts +++ b/src/app/api/related-subject/route.ts @@ -16,6 +16,7 @@ export async function GET(req: NextRequest) { return success({ related_subjects: relatedSubjects }); } catch (error) { - return failure("Failed to fetch related subject", 500, error); + console.error(error); + return failure("Failed to fetch related subject", 500); } } \ No newline at end of file diff --git a/src/app/api/request/route.ts b/src/app/api/request/route.ts index e6dce2d..104b25c 100644 --- a/src/app/api/request/route.ts +++ b/src/app/api/request/route.ts @@ -22,6 +22,6 @@ export async function POST(req: Request) { return success({ message: "Paper request submitted successfully!", request: newRequest }, "Created", 201); } catch (error) { console.error("Error creating paper request:", error); - return failure("Failed to submit request.", 500, error); + return failure("Failed to submit request.", 500); } } diff --git a/src/app/api/selected-papers/route.ts b/src/app/api/selected-papers/route.ts index 1796356..ba02d77 100644 --- a/src/app/api/selected-papers/route.ts +++ b/src/app/api/selected-papers/route.ts @@ -16,6 +16,6 @@ export async function GET() { return success(selectedPapers); } catch (error) { console.error("Error fetching papers:", error); - return failure("Failed to fetch papers.", 500, error); + return failure("Failed to fetch papers.", 500); } } diff --git a/src/app/api/upcoming-papers/route.ts b/src/app/api/upcoming-papers/route.ts index f5b1f7a..1ba624f 100644 --- a/src/app/api/upcoming-papers/route.ts +++ b/src/app/api/upcoming-papers/route.ts @@ -19,6 +19,6 @@ export async function GET() { return success(selectedSubjects); } catch (error) { console.error("Error fetching papers:", error); - return failure("Failed to fetch papers.", 500, error); + return failure("Failed to fetch papers.", 500); } } diff --git a/src/app/api/upload/route.ts b/src/app/api/upload/route.ts index e5264ba..6ce5806 100644 --- a/src/app/api/upload/route.ts +++ b/src/app/api/upload/route.ts @@ -54,6 +54,6 @@ export async function POST(req: Request) { return success({ file_url, thumbnail_url }, "Created", 201); } catch (error) { console.error(error); - return failure("Failed to upload papers", 500, error); + return failure("Failed to upload papers", 500); } } diff --git a/src/app/api/user-papers/route.ts b/src/app/api/user-papers/route.ts index 3c47b45..939d29f 100644 --- a/src/app/api/user-papers/route.ts +++ b/src/app/api/user-papers/route.ts @@ -22,6 +22,6 @@ export async function POST(req: Request) { return success(transformedPapers); } catch (error) { console.error("Error fetching papers:", error); - return failure("Failed to fetch papers.", 500, error); + return failure("Failed to fetch papers.", 500); } } diff --git a/src/interface.ts b/src/interface.ts index 4575a1b..016128f 100644 --- a/src/interface.ts +++ b/src/interface.ts @@ -56,7 +56,6 @@ export interface ApiResponse { status: "success" | "error"; data: T | null; message: string; - error?: any; } export interface ConverttoPDFResponse { diff --git a/src/lib/utils/response.ts b/src/lib/utils/response.ts index b13c0e8..4110509 100644 --- a/src/lib/utils/response.ts +++ b/src/lib/utils/response.ts @@ -14,9 +14,8 @@ export const success = ( export const failure = ( message: string, status = 400, - error?: any ) => NextResponse.json>( - { status: "error", data: null, message, error }, + { status: "error", data: null, message}, { status } ) \ No newline at end of file From 6938b21197d3c460befadd2b2cc3c6287ca29ea5 Mon Sep 17 00:00:00 2001 From: Gslmao Date: Sun, 14 Jun 2026 22:36:58 +0530 Subject: [PATCH 15/15] fix: lint errors --- src/app/actions/get-papers-by-id.ts | 9 ++++----- src/app/upload/page.tsx | 27 ++++++++++++++------------- src/components/Footer.tsx | 3 ++- 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/src/app/actions/get-papers-by-id.ts b/src/app/actions/get-papers-by-id.ts index 3cbbcb6..6909aa6 100644 --- a/src/app/actions/get-papers-by-id.ts +++ b/src/app/actions/get-papers-by-id.ts @@ -1,4 +1,4 @@ -import { type PaperResponse, ApiResponse } from "@/interface"; +import type { PaperResponse, ApiResponse } from "@/interface"; import axios, { type AxiosResponse } from "axios"; export const fetchPaperID = async (id: string): Promise => { @@ -14,10 +14,9 @@ export const fetchPaperID = async (id: string): Promise => { } return response.data.data; } catch (err: unknown) { - if (axios.isAxiosError(err)) { - console.error("Axios error:", err.response?.data.message ?? err.message); - const errorMessage = - (err.response?.data.message ?? "Failed to fetch paper"); + if (axios.isAxiosError>(err)) { + const errorMessage = err.response?.data?.message ?? err.message; + console.error("Axios error:", errorMessage); throw new Error(errorMessage); } else { console.error("Unexpected error:", err); diff --git a/src/app/upload/page.tsx b/src/app/upload/page.tsx index 1a00b3b..cab0010 100644 --- a/src/app/upload/page.tsx +++ b/src/app/upload/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { useCallback, useEffect, useState } from "react"; +import { useCallback, useEffect, useState, useRef } from "react"; import toast from "react-hot-toast"; import { Button } from "@/components/ui/button"; import axios, { AxiosError } from "axios"; @@ -13,7 +13,7 @@ import { TouchSensor, useSensor, useSensors, - DragEndEvent, + type DragEndEvent, } from "@dnd-kit/core"; import { arrayMove, @@ -24,17 +24,12 @@ import { import { CSS } from "@dnd-kit/utilities"; import Dropzone from "react-dropzone"; import { Upload, XIcon } from "lucide-react"; -import { getDocument, GlobalWorkerOptions } from "pdfjs-dist"; -import { type ApiResponse } from "@/interface" +import { GlobalWorkerOptions } from "pdfjs-dist"; +import type { ApiResponse } from "@/interface" GlobalWorkerOptions.workerSrc = "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.8.69/pdf.worker.min.mjs"; -interface APIResponse { - status: string; - message?: string; -} - interface uploadResponse { file_url: string; thumbnail_url: string; @@ -49,6 +44,12 @@ export default function Page() { const [isGlobalDragging, setIsGlobalDragging] = useState(false); const [zoomIndex, setZoomIndex] = useState(null); + const previewsRef = useRef(previews); + + useEffect(() => { + previewsRef.current = previews; + }, [previews]); + useEffect(() => { const onDragEnter = () => setIsGlobalDragging(true); const onDragLeave = () => setIsGlobalDragging(false); @@ -68,7 +69,7 @@ export default function Page() { // Cleanup previews on unmount useEffect(() => { return () => { - previews.forEach((item) => { + previewsRef.current.forEach((item) => { try { URL.revokeObjectURL(item.preview); } catch {} @@ -281,10 +282,10 @@ export default function Page() { await axios.post>("/api/upload", formData); return { message: "Papers uploaded successfully!" }; } catch (error) { - if (error instanceof AxiosError && error.response?.data) { - const errorData = error.response.data; + if (error instanceof AxiosError) { + const errorData = error?.response?.data as ApiResponse; const errorMessage = - errorData.message ?? "Failed to upload papers"; + errorData?.message ?? "Failed to upload papers"; throw new Error(errorMessage); } throw new Error("Failed to upload papers"); diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx index 679d165..464a542 100644 --- a/src/components/Footer.tsx +++ b/src/components/Footer.tsx @@ -15,6 +15,7 @@ import { } from "react-icons/fa6"; import { Bold, Mail } from "lucide-react"; import toast from "react-hot-toast"; +import type { ApiResponse } from '@/interface' type SubscribeResponse = { success?: boolean; @@ -43,7 +44,7 @@ export default function Footer() { body: JSON.stringify({ email }), }) .then(async (res) => { - const data = (await res.json()).data as SubscribeResponse; + const data = (await res.json()) as ApiResponse; if (!res.ok) throw new Error(data.message ?? "Something went wrong."); return data; }),