import { db } from "../../../db/index"; import { uploads, courses } from "../../../db/schema"; import { eq } from "drizzle-orm"; import { randomUUID } from "crypto"; import { writeFile, mkdir } from "fs/promises"; import { resolve } from "path"; import { parsePdf } from "../../../utils/parsePdf"; import { detectUploadType } from "../../../utils/detectUploadType"; const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; export default defineEventHandler(async (event) => { const id = getRouterParam(event, "id")!; if (!UUID_RE.test(id)) { throw createError({ statusCode: 400, message: "Invalid course id" }); } const course = await db.query.courses.findFirst({ where: eq(courses.id, id) }); if (!course) throw createError({ statusCode: 404, message: "Course not found" }); const formData = await readFormData(event); const file = formData.get("file") as File | null; if (!file) { throw createError({ statusCode: 400, message: "file is required" }); } // 50mb limit if (file.size > 50 * 1024 * 1024) { throw createError({ statusCode: 400, message: "File exceeds 50MB limit" }); } const buffer = Buffer.from(await file.arrayBuffer()); // check pdf magic bytes: %PDF = 0x25 0x50 0x44 0x46 if (buffer[0] !== 0x25 || buffer[1] !== 0x50 || buffer[2] !== 0x44 || buffer[3] !== 0x46) { throw createError({ statusCode: 400, message: "File does not appear to be a valid PDF" }); } const uploadDir = resolve(process.cwd(), "uploads", id); await mkdir(uploadDir, { recursive: true }); const uploadId = randomUUID(); const safeFilename = `${uploadId}-${file.name.replace(/[^a-zA-Z0-9._-]/g, "_")}`; // safeFilename shouldnt be just dots/underscores if (/^[._]+$/.test(safeFilename.replace(uploadId + "-", ""))) { throw createError({ statusCode: 400, message: "Invalid filename" }); } const storedPath = resolve(uploadDir, safeFilename); await writeFile(storedPath, buffer); let extractedText: string | null = null; let pdfWarning: string | undefined; try { extractedText = await parsePdf(buffer); } catch (err: any) { console.error(`[upload] PDF text extraction failed for file size ${buffer.length}: ${err?.message ?? err}`); pdfWarning = "PDF text extraction failed"; } const detectedType = await detectUploadType(file.name, extractedText ?? ""); await db.insert(uploads).values({ id: uploadId, courseId: id, filename: file.name, type: detectedType, storedPath, extractedText, }); if (pdfWarning) { return { uploadId, filename: file.name, type: detectedType, warning: pdfWarning }; } return { uploadId, filename: file.name, type: detectedType }; });