From 73b0f81402726aa48ba2d373b16d9587061837ee Mon Sep 17 00:00:00 2001 From: MaryamFareed684 Date: Sat, 18 Jul 2026 12:40:06 +0500 Subject: [PATCH 1/3] docs: add API contract for /upload and /uploads endpoints --- docs/api-contracts.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/docs/api-contracts.md b/docs/api-contracts.md index e69de29..0b95937 100644 --- a/docs/api-contracts.md +++ b/docs/api-contracts.md @@ -0,0 +1,35 @@ +# API Contracts + +## POST /upload +Uploads a single PDF file. + +**Request:** multipart/form-data with a "file" field. + +**Response (200):** +{ + "filename": "notes.pdf", + "upload_date": "2026-07-19T10:15:00", + "file_type": "pdf", + "status": "pending", + "metadata": null +} + +**Response (400):** if file type is invalid +{ + "detail": "Only PDF files are accepted." +} + +## GET /uploads +Returns a list of all uploaded files. + +**Response (200):** +[ + { + "filename": "notes.pdf", + "upload_date": "2026-07-19T10:15:00", + "file_type": "pdf", + "status": "pending", + "metadata": null + }, + ... +] \ No newline at end of file From 165c851cf4b52c9fd11a1f08ad9943eb2e169e72 Mon Sep 17 00:00:00 2001 From: MaryamFareed684 Date: Sat, 18 Jul 2026 01:23:53 -0700 Subject: [PATCH 2/3] feat(frontend): add UploadView and Dashboard components with placeholder data (#10) * feat(frontend): add UploadView component with file picker * feat(frontend): add Dashboard component with placeholder upload list --- web/frontend/src/App.jsx | 5 +++ web/frontend/src/components/Dashboard.jsx | 37 ++++++++++++++++++ web/frontend/src/components/UploadView.jsx | 44 ++++++++++++++++++++++ 3 files changed, 86 insertions(+) create mode 100644 web/frontend/src/components/Dashboard.jsx create mode 100644 web/frontend/src/components/UploadView.jsx diff --git a/web/frontend/src/App.jsx b/web/frontend/src/App.jsx index 893d964..1097724 100644 --- a/web/frontend/src/App.jsx +++ b/web/frontend/src/App.jsx @@ -1,5 +1,7 @@ import { useState, useEffect } from 'react'; import './App.css'; +import UploadView from './components/UploadView'; +import Dashboard from './components/Dashboard'; function App() { const [status, setStatus] = useState('checking...'); @@ -15,6 +17,9 @@ function App() {

Quantum Learning Workspace

Backend status : {status}

+ + +
); } diff --git a/web/frontend/src/components/Dashboard.jsx b/web/frontend/src/components/Dashboard.jsx new file mode 100644 index 0000000..21efb8d --- /dev/null +++ b/web/frontend/src/components/Dashboard.jsx @@ -0,0 +1,37 @@ +import { useState } from "react"; + +function Dashboard() { + // This is our FAKE list of uploads, just for now. + // It has the exact same shape we expect the real /uploads + // endpoint to send us later (filename, upload_date, etc.) + const [uploads, setUploads] = useState([ + { + filename: "chapter1-notes.pdf", + upload_date: "2026-07-15T10:00:00", + }, + { + filename: "physics-lecture.pdf", + upload_date: "2026-07-16T14:30:00", + }, + { + filename: "research-paper.pdf", + upload_date: "2026-07-17T09:15:00", + }, + ]); + + return ( +
+

Dashboard — Uploaded Files

+ +
    + {uploads.map((upload) => ( +
  • + {upload.filename} — {upload.upload_date} +
  • + ))} +
+
+ ); +} + +export default Dashboard; \ No newline at end of file diff --git a/web/frontend/src/components/UploadView.jsx b/web/frontend/src/components/UploadView.jsx new file mode 100644 index 0000000..d75b432 --- /dev/null +++ b/web/frontend/src/components/UploadView.jsx @@ -0,0 +1,44 @@ +import { useState } from "react"; + +function UploadView() { + // This "box" remembers which file the user picked. + // Right now it's empty (null) because no file is chosen yet. + const [selectedFile, setSelectedFile] = useState(null); + + // This function runs automatically when the user picks a file + // using the file picker window. + function handleFileChange(event) { + const file = event.target.files[0]; // grab the first picked file + setSelectedFile(file); // save it into our "box" + } + + // This function runs when the user clicks the Upload button. + // For now, it just shows the file name in the console — + // we are NOT sending it to the backend yet. + function handleUploadClick() { + if (!selectedFile) { + alert("Please choose a file first."); + return; + } + console.log("File ready to upload:", selectedFile.name); + } + + return ( +
+

Upload a File

+ + {/* This is the file picker itself */} + + + {/* This button triggers the upload action */} + + + {/* A little message showing what file is currently picked */} + {selectedFile &&

Selected file: {selectedFile.name}

} +
+ ); +} + +export default UploadView; \ No newline at end of file From 1b2964a9d6750ea88c3d429a873f6c0d58952103 Mon Sep 17 00:00:00 2001 From: Abdullah Razzaq Date: Sun, 19 Jul 2026 03:16:22 +0500 Subject: [PATCH 3/3] Add /upload and /uploads endpoints with PDF validation --- web/backend/main.py | 6 +++-- web/backend/routes/upload.py | 50 ++++++++++++++++++++++++++++++++++++ web/backend/uploads/.gitkeep | 0 3 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 web/backend/routes/upload.py create mode 100644 web/backend/uploads/.gitkeep diff --git a/web/backend/main.py b/web/backend/main.py index d3eff8c..56de7d6 100644 --- a/web/backend/main.py +++ b/web/backend/main.py @@ -1,16 +1,18 @@ from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware +from routes.upload import router as upload_router # ADD THIS app = FastAPI(title="StudyMind AI Backend") -# Allow the React dev server to talk to this API app.add_middleware( CORSMiddleware, - allow_origins=["http://localhost:5173"], # Vite's default port + allow_origins=["http://localhost:5173"], allow_methods=["*"], allow_headers=["*"], ) +app.include_router(upload_router) # ADD THIS + @app.get("/health") def health_check(): return {"status": "ok"} \ No newline at end of file diff --git a/web/backend/routes/upload.py b/web/backend/routes/upload.py new file mode 100644 index 0000000..62ca142 --- /dev/null +++ b/web/backend/routes/upload.py @@ -0,0 +1,50 @@ +import os +from datetime import datetime +from fastapi import APIRouter, UploadFile, File, HTTPException + +from database import get_uploads_collection +from models import Upload + +router = APIRouter() + +UPLOAD_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "uploads") + + +@router.post("/upload") +async def upload_file(file: UploadFile = File(...)): + # Stretch goal: only accept PDFs + if not file.filename.lower().endswith(".pdf"): + raise HTTPException(status_code=400, detail="Only PDF files are accepted") + + # Avoid overwriting files with the same name + timestamp = datetime.utcnow().strftime("%Y%m%d%H%M%S") + safe_filename = f"{timestamp}_{file.filename}" + file_path = os.path.join(UPLOAD_DIR, safe_filename) + + os.makedirs(UPLOAD_DIR, exist_ok=True) + with open(file_path, "wb") as f: + content = await file.read() + f.write(content) + + upload_record = Upload( + filename=safe_filename, + file_type=file.content_type or "application/pdf", + status="pending", + ) + + collection = get_uploads_collection() + await collection.insert_one(upload_record.model_dump()) + + return { + "filename": safe_filename, + "status": "pending", + "message": "File uploaded successfully", + } + + +@router.get("/uploads") +async def list_uploads(): + collection = get_uploads_collection() + cursor = collection.find({}, {"_id": 0}) + uploads = await cursor.to_list(length=100) + return {"uploads": uploads} \ No newline at end of file diff --git a/web/backend/uploads/.gitkeep b/web/backend/uploads/.gitkeep new file mode 100644 index 0000000..e69de29