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 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 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() {
Backend status : {status}
+ +Selected file: {selectedFile.name}
} +