diff --git a/src/app/api/upload/route.ts b/src/app/api/upload/route.ts index 6ce5806b..67d4fb50 100644 --- a/src/app/api/upload/route.ts +++ b/src/app/api/upload/route.ts @@ -1,11 +1,14 @@ import { connectToDatabase } from "@/lib/database/mongoose"; import { PaperAdmin } from "@/db/papers"; -import { createPDFfromImages } from "@/lib/storage/pdf"; +import { createPDFfromImages, compressPDF } from "@/lib/storage/pdf"; import { uploadPDF, uploadThumbnail } from "@/lib/storage/gcp"; import { success, failure } from "@/lib/utils/response"; export const runtime = "nodejs"; +const MAX_COMPRESSED_PDF_SIZE = 5 * 1024 * 1024; // 5MB compressed +const COMPRESS_THRESHOLD = 5 * 1024 * 1024; // 5MB + export async function POST(req: Request) { try { await connectToDatabase(); @@ -23,9 +26,31 @@ export async function POST(req: Request) { if (!files[0]) { return failure("No PDF file provided.", 400); } - pdfBytes = new Uint8Array(await files[0].arrayBuffer()); + + const rawPdfBytes = new Uint8Array(await files[0].arrayBuffer()); + if (rawPdfBytes.length > COMPRESS_THRESHOLD) { + const compressedPdfBytes = await compressPDF(rawPdfBytes); + pdfBytes = compressedPdfBytes.length <= rawPdfBytes.length + ? compressedPdfBytes + : rawPdfBytes; + + if (pdfBytes.length > MAX_COMPRESSED_PDF_SIZE) { + return failure( + "PDF is too large after compression. The compressed file must be under 5MB.", + 413, + ); + } + } else { + pdfBytes = rawPdfBytes; + } } else { pdfBytes = await createPDFfromImages(files); + if (pdfBytes.length > MAX_COMPRESSED_PDF_SIZE) { + return failure( + "Generated PDF is too large after compression. Please upload fewer or smaller images.", + 413, + ); + } } const buffer = Buffer.from(pdfBytes); diff --git a/src/app/upload/page.tsx b/src/app/upload/page.tsx index a1a8c495..dbabed99 100644 --- a/src/app/upload/page.tsx +++ b/src/app/upload/page.tsx @@ -25,7 +25,7 @@ import { CSS } from "@dnd-kit/utilities"; import Dropzone from "react-dropzone"; import { Upload, XIcon } from "lucide-react"; import { GlobalWorkerOptions } from "pdfjs-dist"; -import type { ApiResponse } from "@/interface" +import type { ApiResponse } from "@/interface"; GlobalWorkerOptions.workerSrc = "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.8.69/pdf.worker.min.mjs"; @@ -79,7 +79,6 @@ export default function Page() { const fileCheckAndSelect = useCallback( (acceptedFiles: File[]) => { - const maxFileSize = 5 * 1024 * 1024; const allowedFileTypes = [ "application/pdf", "image/jpeg", @@ -137,28 +136,18 @@ export default function Page() { } const allFiles = [...files, ...acceptedFiles]; - if (allFiles.length > 5) { - toast.error("You can upload up to 5 files only", { id: toastId }); - return; - } - - const totalSize = allFiles.reduce( - (sum, f) => sum + f.size, - 0, - ); - if (totalSize > maxFileSize){ - toast.error("The total upload size exceeds 5MB.", { id: toastId }); + if (allFiles.length > 10) { + toast.error("You can upload up to 10 files only", { id: toastId }); return; } const invalidFiles = acceptedFiles.filter( - (file) => - file.size > maxFileSize || !allowedFileTypes.includes(file.type), + (file) => !allowedFileTypes.includes(file.type), ); if (invalidFiles.length > 0) { toast.error( - "Some files are invalid. Make sure the total size is below 5MB and files are of allowed types (PDF, JPEG, PNG, GIF).", + "Some files are invalid. Make sure they are PDFs, JPEGs, PNGs, or GIFs.", { id: toastId }, ); return; @@ -279,7 +268,10 @@ 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) { @@ -447,12 +439,14 @@ export default function Page() { ); return ( -
+ className="mt-6 flex w-full flex-col items-center" + >
-
j +
+ j )}
- ) + ); }} - )}