Prashu15/FeedbackGenerator
0
1from fastapi import FastAPI, UploadFile, File, HTTPException, Depends, status2from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm3from fastapi.responses import JSONResponse, FileResponse4from fastapi.staticfiles import StaticFiles5from pathlib import Path6import os7from feedback_logic import evaluate_assignment8from pydantic import BaseModel9from passlib.context import CryptContext10import sqlite311from jose import JWTError, jwt12from datetime import datetime, timedelta13import secrets14 15app = FastAPI(title="Automatic Feedback Generator")16 17# Mount static files directory18app.mount("/static", StaticFiles(directory="static"), name="static")19 20# Configuration21TEMP_DIR = Path("/tmp/uploads")22TEMP_DIR.mkdir(parents=True, exist_ok=True)23SUPPORTED_FORMATS = {".pdf", ".doc", ".docx", ".png", ".jpg", ".jpeg"}24SECRET_KEY = secrets.token_urlsafe(32)25ALGORITHM = "HS256"26ACCESS_TOKEN_EXPIRE_MINUTES = 3027 28# Database setup29DB_PATH = "/tmp/users.db"30def init_db():31 with sqlite3.connect(DB_PATH) as conn:32 c = conn.cursor()33 c.execute('''CREATE TABLE IF NOT EXISTS users (34 id INTEGER PRIMARY KEY AUTOINCREMENT,35 email TEXT UNIQUE NOT NULL,36 password TEXT NOT NULL,37 name TEXT NOT NULL38 )''')39 conn.commit()40init_db()41 42# Password hashing43pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")44oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")45 46# Pydantic models47class User(BaseModel):48 email: str49 name: str50 password: str51 52class Token(BaseModel):53 access_token: str54 token_type: str55 56# Authentication functions57def verify_password(plain_password, hashed_password):58 return pwd_context.verify(plain_password, hashed_password)59 60def get_password_hash(password):61 return pwd_context.hash(password)62 63def create_access_token(data: dict):64 to_encode = data.copy()65 expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)66 to_encode.update({"exp": expire})67 encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)68 return encoded_jwt69 70async def get_current_user(token: str = Depends(oauth2_scheme)):71 credentials_exception = HTTPException(72 status_code=status.HTTP_401_UNAUTHORIZED,73 detail="Could not validate credentials",74 headers={"WWW-Authenticate": "Bearer"},75 )76 try:77 payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])78 email: str = payload.get("sub")79 if email is None:80 raise credentials_exception81 except JWTError:82 raise credentials_exception83 with sqlite3.connect(DB_PATH) as conn:84 c = conn.cursor()85 c.execute("SELECT email, name FROM users WHERE email = ?", (email,))86 user = c.fetchone()87 if user is None:88 raise credentials_exception89 return {"email": user[0], "name": user[1]}90 91@app.post("/register/")92async def register(user: User):93 try:94 with sqlite3.connect(DB_PATH) as conn:95 c = conn.cursor()96 hashed_password = get_password_hash(user.password)97 c.execute(98 "INSERT INTO users (email, name, password) VALUES (?, ?, ?)",99 (user.email, user.name, hashed_password)100 )101 conn.commit()102 return {"message": "User registered successfully"}103 except sqlite3.IntegrityError:104 raise HTTPException(status_code=400, detail="Email already registered")105 106@app.post("/token", response_model=Token)107async def login(form_data: OAuth2PasswordRequestForm = Depends()):108 with sqlite3.connect(DB_PATH) as conn:109 c = conn.cursor()110 c.execute("SELECT email, password FROM users WHERE email = ?", (form_data.username,))111 user = c.fetchone()112 if not user or not verify_password(form_data.password, user[1]):113 raise HTTPException(114 status_code=status.HTTP_401_UNAUTHORIZED,115 detail="Incorrect email or password",116 headers={"WWW-Authenticate": "Bearer"},117 )118 access_token = create_access_token(data={"sub": user[0]})119 return {"access_token": access_token, "token_type": "bearer"}120 121@app.post("/evaluate/")122async def evaluate(123 assignment: UploadFile = File(...),124 rubric: UploadFile = File(...),125 current_user: dict = Depends(get_current_user)126):127 try:128 # Validate file extensions129 assignment_ext = os.path.splitext(assignment.filename)[1].lower()130 rubric_ext = os.path.splitext(rubric.filename)[1].lower()131 132 if assignment_ext not in SUPPORTED_FORMATS:133 raise HTTPException(status_code=400, detail=f"Unsupported assignment file format: {assignment_ext}")134 if rubric_ext not in {".pdf", ".doc", ".docx"}:135 raise HTTPException(status_code=400, detail=f"Unsupported rubric file format: {rubric_ext}")136 137 # Save uploaded files temporarily138 assignment_path = TEMP_DIR / assignment.filename139 rubric_path = TEMP_DIR / rubric.filename140 141 try:142 # Write assignment file143 with open(assignment_path, "wb") as f:144 content = await assignment.read()145 if len(content) > 100 * 1024 * 1024: # Limit to 100MB146 raise HTTPException(status_code=400, detail="Assignment file too large (max 100MB)")147 f.write(content)148 149 # Write rubric file150 with open(rubric_path, "wb") as f:151 content = await rubric.read()152 if len(content) > 10 * 1024 * 1024: # Limit to 10MB for rubric153 raise HTTPException(status_code=400, detail="Rubric file too large (max 10MB)")154 f.write(content)155 156 # Evaluate assignment157 feedback = evaluate_assignment(str(assignment_path), str(rubric_path))158 return JSONResponse(content={"feedback": feedback, "user": current_user["email"]})159 160 finally:161 # Clean up temporary files162 if assignment_path.exists():163 os.remove(assignment_path)164 if rubric_path.exists():165 os.remove(rubric_path)166 167 except Exception as e:168 raise HTTPException(status_code=500, detail=f"Error processing files: {str(e)}")169 170@app.get("/")171async def root():172 return FileResponse("static/index.html")173 174@app.get("/index.html")175async def serve_index():176 return FileResponse("static/index.html")