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: 2 additions & 0 deletions src/app/api/paper-by-id/[id]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { Types } from "mongoose";
import { getPaperById } from "@/lib/services/paper";
import { success, failure } from "@/lib/utils/response"

export const dynamic = "force-dynamic";

export async function GET(req: Request, { params }: { params: { id: string } }) {
try {
const { id } = params;
Expand Down
16 changes: 4 additions & 12 deletions src/app/api/request/route.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,16 @@
import { connectToDatabase } from "@/lib/database/mongoose";
import PaperRequest from "@/db/paperRequest";
import { success, failure } from "@/lib/utils/response";
import { createPaperRequest } from "@/lib/services/paper"
import type { CreatePaperInputType } from "@/lib/services/paper"

export async function POST(req: Request) {
try {
await connectToDatabase();
const body = (await req.json()) as {
subject: string;
exam: string;
slot: string;
year: string;
};

const { subject, exam, slot, year } = body;
const {subject, exam, slot, year} = (await req.json()) as CreatePaperInputType

if (!subject || !exam || !slot || !year) {
return failure("All fields are required.", 400);
}

const newRequest = await PaperRequest.create({ subject, exam, slot, year });
const newRequest = await createPaperRequest({subject, exam, slot, year});
return success({ message: "Paper request submitted successfully!", request: newRequest }, "Created", 201);
} catch (error) {
console.error("Error creating paper request:", error);
Expand Down
9 changes: 3 additions & 6 deletions src/app/api/selected-papers/route.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import { connectToDatabase } from "@/lib/database/mongoose";
import Paper from "@/db/papers";
import { success, failure } from "@/lib/utils/response";
import { getSelectedPapers } from "@/lib/services/paper";

export const dynamic = "force-dynamic";

export async function GET() {
try {
await connectToDatabase();

const selectedPapers = await Paper.find({ isSelected: true }).limit(8);
const selectedPapers = await getSelectedPapers();

if (selectedPapers.length === 0) {
return failure("No selected papers found.", 404);
Expand All @@ -18,4 +15,4 @@ export async function GET() {
console.error("Error fetching papers:", error);
return failure("Failed to fetch papers.", 500);
}
}
}
13 changes: 4 additions & 9 deletions src/app/api/upcoming-papers/route.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
import { connectToDatabase } from "@/lib/database/mongoose";
import UpcomingSubject from "@/db/upcoming-paper";
import { success, failure } from "@/lib/utils/response";
import { getUpcomingSubjects } from "@/lib/services/subject";

export const dynamic = "force-dynamic";

export async function GET() {
try {
await connectToDatabase();
const selectedSubjects = await UpcomingSubject.find()
.sort({ _id: 1 })
.limit(16)
.lean();

const selectedSubjects = await getUpcomingSubjects();

if (selectedSubjects.length === 0) {
return failure("No selected papers found.", 404);
}
Expand All @@ -21,4 +16,4 @@ export async function GET() {
console.error("Error fetching papers:", error);
return failure("Failed to fetch papers.", 500);
}
}
}
75 changes: 12 additions & 63 deletions src/app/api/upload/route.ts
Original file line number Diff line number Diff line change
@@ -1,84 +1,33 @@
import { connectToDatabase } from "@/lib/database/mongoose";
import { PaperAdmin } from "@/db/papers";
import { createPDFfromImages, compressPDF } from "@/lib/storage/pdf";
import { uploadPDF, uploadThumbnail } from "@/lib/storage/gcp";
import { success, failure } from "@/lib/utils/response";
import { uploadPaper } from "@/lib/services/upload";

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();
const formData = await req.formData();
const files = formData.getAll("files").filter(Boolean) as File[];
const isPdf = formData.get("isPdf") === "true";
const thumb = formData.get("thumbnail") as File | null;
const thumbnail = formData.get("thumbnail") as File | null;
const campus = formData.get("campus") as string | null;

if (files.length === 0) {
return failure("No files received.", 400);
}

let pdfBytes: Uint8Array;
if (isPdf) {
if (!files[0]) {
return failure("No PDF file provided.", 400);
}

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;
const result = await uploadPaper({ files, isPdf, thumbnail, campus });

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,
);
}
if (!result.success) {
return failure(result.message, result.status);
}

const buffer = Buffer.from(pdfBytes);

const file_url = await uploadPDF("unapproved", buffer);

let thumbnail_url: string | null = null;
if (thumb) {
const thumbBuffer = Buffer.from(await thumb.arrayBuffer());
thumbnail_url = await uploadThumbnail(thumbBuffer, file_url);
}

const paper = new PaperAdmin({
file_url,
thumbnail_url,
campus: formData.get("campus"),
subject: null,
slot: null,
year: null,
exam: null,
semester: null,
ambiguous_tags: [],
});
await paper.save();

return success({ file_url, thumbnail_url }, "Created", 201);
return success(
{ file_url: result.file_url, thumbnail_url: result.thumbnail_url },
"Created",
201,
);
} catch (error) {
console.error(error);
return failure("Failed to upload papers", 500);
}
}
}
13 changes: 2 additions & 11 deletions src/app/api/user-papers/route.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,14 @@
import { connectToDatabase } from "@/lib/database/mongoose";
import Paper from "@/db/papers";
import { type StoredSubjects } from "@/interface";
import { transformPapersToSubjectSlots } from "@/lib/services/paper-transform";
import { getPapersBySubjects } from "@/lib/services/paper"
import { success, failure } from "@/lib/utils/response";

export const dynamic = "force-dynamic";

export async function POST(req: Request) {
try {
await connectToDatabase();
const subjects = (await req.json()) as StoredSubjects;

const usersPapers = await Paper.find({
subject: { $in: subjects },
});

console.log("Fetched user papers:", usersPapers);

const transformedPapers = transformPapersToSubjectSlots(usersPapers);
const transformedPapers = await getPapersBySubjects(subjects)

return success(transformedPapers);
} catch (error) {
Expand Down
30 changes: 30 additions & 0 deletions src/lib/services/paper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ import { escapeRegExp } from "@/lib/utils/regex";
import { extractUniqueValues } from "@/lib/utils/paper-aggregation";
import { connectToDatabase } from "../database/mongoose";
import CourseCount from "@/db/course";
import PaperRequest from "@/db/paperRequest";
import { type StoredSubjects } from "@/interface";
import { transformPapersToSubjectSlots } from "@/lib/services/paper-transform";

export interface CreatePaperInputType {
subject: string;
exam: string;
slot: string;
year: string;
}

export async function getPapersBySubject(subject: string) {
if (!subject){
Expand Down Expand Up @@ -47,4 +57,24 @@ export async function getCourseCounts(){
}));

return formatted;
}

export async function createPaperRequest({ subject, exam, slot, year} : CreatePaperInputType){
await connectToDatabase();
return await PaperRequest.create({ subject, exam, slot, year });
}

export async function getSelectedPapers() {
await connectToDatabase();
return await Paper.find({ isSelected: true }).limit(8);
}

export async function getPapersBySubjects(subjects: StoredSubjects) {
await connectToDatabase();

const usersPapers = await Paper.find({
subject: { $in: subjects },
});

return transformPapersToSubjectSlots(usersPapers);
}
9 changes: 9 additions & 0 deletions src/lib/services/subject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { type IRelatedSubject } from "@/interface";
import { escapeRegExp } from "@/lib/utils/regex";
import { Course } from "@/db/course";
import RelatedSubject from "@/db/relatedSubjects";
import UpcomingSubject from "@/db/upcoming-paper";

export async function getCourseList(){
await connectToDatabase();
Expand All @@ -17,4 +18,12 @@ export async function getRelatedSubjects(subject: string) {
});

return subjects[0]?.related_subjects ?? [];
}

export async function getUpcomingSubjects() {
await connectToDatabase();
return await UpcomingSubject.find()
.sort({ _id: 1 })
.limit(16)
.lean();
}
85 changes: 85 additions & 0 deletions src/lib/services/upload.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { PaperAdmin } from "@/db/papers";
import { createPDFfromImages, compressPDF } from "@/lib/storage/pdf";
import { uploadPDF, uploadThumbnail } from "@/lib/storage/gcp";
import { connectToDatabase } from "@/lib/database/mongoose";

const MAX_COMPRESSED_PDF_SIZE = 5 * 1024 * 1024; // 5MB compressed
const COMPRESS_THRESHOLD = 5 * 1024 * 1024; // 5MB

type UploadPaperInput = {
files: File[];
isPdf: boolean;
thumbnail: File | null;
campus: string | null;
};

type UploadPaperResult =
| { success: true; file_url: string; thumbnail_url: string | null }
| { success: false; message: string; status: number };

export async function uploadPaper({
files,
isPdf,
thumbnail,
campus,
}: UploadPaperInput): Promise<UploadPaperResult> {
await connectToDatabase();

let pdfBytes: Uint8Array;

if (isPdf) {
if (!files[0]) {
return { success: false, message: "No PDF file provided.", status: 400 };
}

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 {
success: false,
message: "PDF is too large after compression. The compressed file must be under 5MB.",
status: 413,
};
}
} else {
pdfBytes = rawPdfBytes;
}
} else {
pdfBytes = await createPDFfromImages(files);
if (pdfBytes.length > MAX_COMPRESSED_PDF_SIZE) {
return {
success: false,
message: "Generated PDF is too large after compression. Please upload fewer or smaller images.",
status: 413,
};
}
}

const buffer = Buffer.from(pdfBytes);
const file_url = await uploadPDF("unapproved", buffer);

let thumbnail_url: string | null = null;
if (thumbnail) {
const thumbBuffer = Buffer.from(await thumbnail.arrayBuffer());
thumbnail_url = await uploadThumbnail(thumbBuffer, file_url);
}

const paper = new PaperAdmin({
file_url,
thumbnail_url,
campus,
subject: null,
slot: null,
year: null,
exam: null,
semester: null,
ambiguous_tags: [],
});
await paper.save();

return { success: true, file_url, thumbnail_url };
}
Loading