Skip to content

Commit 6e0571e

Browse files
committed
refactor: separated the core logic from route file
1 parent 1ecd37a commit 6e0571e

4 files changed

Lines changed: 88 additions & 92 deletions

File tree

src/app/api/upload/route.ts

Lines changed: 28 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,58 @@
11
import { NextResponse } from "next/server";
2-
import { PDFDocument } from "pdf-lib";
32
import { connectToDatabase } from "@/lib/mongoose";
43
import { PaperAdmin } from "@/db/papers";
5-
import { Storage } from "@google-cloud/storage";
4+
import { createPDFfromImages } from "@/lib/pdf";
5+
import { uploadPDF, uploadThumbnail } from "@/lib/storage";
66

7-
interface GCPCredentials {
8-
type: string;
9-
project_id: string;
10-
private_key_id: string;
11-
private_key: string;
12-
client_email: string;
13-
client_id: string;
14-
auth_uri: string;
15-
token_uri: string;
16-
auth_provider_x509_cert_url: string;
17-
client_x509_cert_url: string;
18-
universe_domain?: string;
19-
}
20-
21-
// Initialize GCP Storage
22-
const credentials: GCPCredentials = JSON.parse(
23-
process.env.GOOGLE_APPLICATION_CREDENTIALS_JSON ?? "{}",
24-
) as GCPCredentials;
25-
26-
const storage = new Storage({
27-
projectId: process.env.GOOGLE_CLOUD_PROJECT,
28-
credentials,
29-
});
30-
31-
const bucketName = process.env.GOOGLE_CLOUD_BUCKET ?? "";
32-
const bucket = storage.bucket(bucketName);
7+
export const runtime = "nodejs";
338

349
export async function POST(req: Request) {
3510
try {
3611
await connectToDatabase();
37-
3812
const formData = await req.formData();
39-
const files = formData.getAll("files") as File[];
13+
const files = formData.getAll("files").filter(Boolean) as File[];
4014
const isPdf = formData.get("isPdf") === "true";
4115
const thumb = formData.get("thumbnail") as File | null;
4216

43-
console.log("Received thumbnail:", thumb ? thumb.name : "NULL");
44-
45-
46-
if (!files || files.length === 0) {
47-
return NextResponse.json({ error: "No files received." }, { status: 400 });
17+
if (files.length === 0) {
18+
return NextResponse.json(
19+
{ error: "No files received." },
20+
{ status: 400 },
21+
);
4822
}
4923

50-
let pdfBytes: ArrayBuffer;
51-
if (isPdf && files[0]) {
52-
pdfBytes = await files[0].arrayBuffer();
24+
let pdfBytes: Uint8Array;
25+
if (isPdf) {
26+
if (!files[0]) {
27+
return NextResponse.json(
28+
{ error: "No PDF file provided." },
29+
{ status: 400 },
30+
);
31+
}
32+
pdfBytes = new Uint8Array(await files[0].arrayBuffer());
5333
} else {
5434
pdfBytes = await createPDFfromImages(files);
5535
}
5636

57-
const { file_url, thumbnail_url } = await uploadToGCS(pdfBytes, thumb);
37+
const buffer = Buffer.from(pdfBytes);
38+
39+
const file_url = await uploadPDF(buffer);
40+
41+
let thumbnail_url: string | null = null;
42+
if (thumb) {
43+
const thumbBuffer = Buffer.from(await thumb.arrayBuffer());
44+
thumbnail_url = await uploadThumbnail(thumbBuffer, file_url);
45+
}
5846

59-
// Save in MongoDB
6047
const paper = new PaperAdmin({
6148
file_url,
6249
thumbnail_url,
50+
campus: formData.get("campus"),
6351
subject: null,
6452
slot: null,
6553
year: null,
6654
exam: null,
6755
semester: null,
68-
campus: formData.get("campus"),
6956
ambiguous_tags: [],
7057
});
7158
await paper.save();
@@ -82,53 +69,3 @@ export async function POST(req: Request) {
8269
);
8370
}
8471
}
85-
86-
async function uploadToGCS(bytes: ArrayBuffer, thumbFile?: File | null) {
87-
const buffer = Buffer.from(bytes);
88-
89-
const pdfFilename = `papers/${Date.now()}-${Math.random()
90-
.toString(36)
91-
.substring(2)}.pdf`;
92-
await bucket.file(pdfFilename).save(buffer, {
93-
resumable: false,
94-
contentType: "application/pdf",
95-
});
96-
const file_url = `https://storage.googleapis.com/${bucketName}/${pdfFilename}`;
97-
98-
let thumbnail_url: string | null = null;
99-
if (thumbFile) {
100-
const thumbBuffer = Buffer.from(await thumbFile.arrayBuffer());
101-
const thumbFilename = pdfFilename.replace(".pdf", ".png");
102-
await bucket.file(thumbFilename).save(thumbBuffer, {
103-
resumable: false,
104-
contentType: "image/png",
105-
});
106-
thumbnail_url = `https://storage.googleapis.com/${bucketName}/${thumbFilename}`;
107-
}
108-
109-
return { file_url, thumbnail_url };
110-
}
111-
112-
async function createPDFfromImages(files: File[]) {
113-
const pdfDoc = await PDFDocument.create();
114-
115-
for (const file of files) {
116-
const imgBytes = Buffer.from(await file.arrayBuffer());
117-
let img;
118-
if (file.type === "image/png") {
119-
img = await pdfDoc.embedPng(imgBytes);
120-
} else if (file.type === "image/jpeg" || file.type === "image/jpg") {
121-
img = await pdfDoc.embedJpg(imgBytes);
122-
} else continue;
123-
124-
const page = pdfDoc.addPage([img.width, img.height]);
125-
page.drawImage(img, { x: 0, y: 0, width: img.width, height: img.height });
126-
}
127-
128-
const mergedPdfBytes = await pdfDoc.save();
129-
const ab = new ArrayBuffer(mergedPdfBytes.byteLength);
130-
new Uint8Array(ab).set(mergedPdfBytes);
131-
return ab;
132-
}
133-
134-
export const runtime = "nodejs";

src/components/pdfViewer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import Loader from "./ui/loader";
1212
import { FaGreaterThan, FaLessThan } from "react-icons/fa6";
1313

1414
pdfjs.GlobalWorkerOptions.workerSrc = new URL(
15-
"pdfjs-dist/build/pdf.worker.min.mjs",
15+
'pdfjs-dist/build/pdf.worker.min.mjs',
1616
import.meta.url,
1717
).toString();
1818

src/lib/pdf.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { PDFDocument } from "pdf-lib";
2+
3+
export async function createPDFfromImages(files: File[]): Promise<Uint8Array> {
4+
const pdfDoc = await PDFDocument.create();
5+
6+
for (const file of files) {
7+
const imgBytes = Buffer.from(await file.arrayBuffer());
8+
let img;
9+
if (file.type === "image/png") {
10+
img = await pdfDoc.embedPng(imgBytes);
11+
} else if (file.type === "image/jpeg" || file.type === "image/jpg") {
12+
img = await pdfDoc.embedJpg(imgBytes);
13+
} else continue;
14+
15+
const page = pdfDoc.addPage([img.width, img.height]);
16+
page.drawImage(img, { x: 0, y: 0, width: img.width, height: img.height });
17+
}
18+
19+
return pdfDoc.save();
20+
}

src/lib/storage.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { Storage, StorageOptions } from "@google-cloud/storage";
2+
3+
interface GCPCredentials {
4+
type: string;
5+
project_id: string;
6+
private_key_id: string;
7+
private_key: string;
8+
client_email: string;
9+
client_id: string;
10+
auth_uri: string;
11+
token_uri: string;
12+
auth_provider_x509_cert_url: string;
13+
client_x509_cert_url: string;
14+
universe_domain?: string;
15+
}
16+
17+
const credentials: GCPCredentials = JSON.parse(
18+
process.env.GOOGLE_APPLICATION_CREDENTIALS_JSON ?? "{}"
19+
) as GCPCredentials;
20+
21+
const storage = new Storage({
22+
projectId: process.env.GOOGLE_CLOUD_PROJECT,
23+
credentials,
24+
} as StorageOptions);
25+
26+
const bucketName = process.env.GOOGLE_CLOUD_BUCKET ?? "";
27+
const bucket = storage.bucket(bucketName);
28+
29+
export async function uploadPDF(buffer: Buffer) {
30+
const pdfFilename = `papers/${Date.now()}-${Math.random().toString(36).substring(2)}.pdf`;
31+
await bucket.file(pdfFilename).save(buffer, { resumable: false, contentType: "application/pdf" });
32+
return `https://storage.googleapis.com/${bucketName}/${pdfFilename}`;
33+
}
34+
35+
export async function uploadThumbnail(buffer: Buffer, pdfFilename: string) {
36+
const thumbFilename = pdfFilename.replace(".pdf", ".png");
37+
await bucket.file(thumbFilename).save(buffer, { resumable: false, contentType: "image/png" });
38+
return `https://storage.googleapis.com/${bucketName}/${thumbFilename}`;
39+
}

0 commit comments

Comments
 (0)