from fastapi import FastAPI, HTTPException, Query from fastapi.responses import JSONResponse from fastapi.middleware.cors import CORSMiddleware import sqlite3 from typing import List, Dict import random from pydantic import BaseModel app = FastAPI() # Configure CORS to allow only specific domains origins = ["*"] app.add_middleware( CORSMiddleware, allow_origins=origins, # Restrict to specific domains in production allow_credentials=True, allow_methods=["*"], # Allows all HTTP methods allow_headers=["*"], # Allows all headers ) # Database connection utility def get_db_connection(): connection = sqlite3.connect("questions.db") connection.row_factory = sqlite3.Row # refer to this SO thread: https://stackoverflow.com/questions/44009452/what-is-the-purpose-of-the-row-factory-method-of-an-sqlite3-connection-object return connection # API endpoints @app.get("/") def get_image(): return { "message": "success", "repository": "https://github.com/qpump/api" } @app.get("/questions") def filter_questions( subject: str = None, chapter: str = None, topic: str = None, question_type: str = None, paper_id: str = None, offset: int = Query(0, ge=0, description="Offset for pagination"), limit: int = Query(10, ge=1, le=100, description="Limit for pagination") ): """Filter questions based on various parameters.""" query = "SELECT * FROM questions WHERE 1=1" params = [] if subject: query += " AND subject = ?" params.append(subject) if chapter: query += " AND chapter = ?" params.append(chapter) if topic: query += " AND topic = ?" params.append(topic) if question_type: query += " AND question_type = ?" params.append(question_type) if paper_id: query += " AND paper_id = ?" params.append(paper_id) query += " LIMIT ? OFFSET ?" params.extend([limit, offset]) connection = get_db_connection() cursor = connection.execute(query, params) questions = cursor.fetchall() connection.close() if not questions: raise HTTPException(status_code=404, detail="No questions found with the given filters.") return [dict(question) for question in questions] @app.get("/questions/{question_id}") def get_question_by_id(question_id: str): """Get question details based on question ID.""" connection = get_db_connection() query = "SELECT * FROM questions WHERE question_id = ?" question = connection.execute(query, (question_id,)).fetchone() connection.close() if not question: raise HTTPException(status_code=404, detail="Question not found.") return dict(question) @app.get("/{subject}") def list_chapters(subject: str): """ Return list of chapters for a given subject along with the count of questions in each chapter. Also includes the total number of questions in the subject. """ connection = get_db_connection() query = "SELECT chapter, COUNT(*) as question_count FROM questions WHERE subject = ? GROUP BY chapter" cursor = connection.execute(query, (subject,)) chapters = cursor.fetchall() connection.close() if not chapters: raise HTTPException(status_code=404, detail="Subject not found or no chapters available.") return { "subject": subject, "chapters": [dict(chapter) for chapter in chapters] } @app.get("/{subject}/{chapter}") def list_topics(subject: str, chapter: str): """ Return list of topics for a given chapter along with the count of questions in each topic. Also includes the total number of questions in the chapter. """ connection = get_db_connection() query = "SELECT topic, COUNT(*) as question_count FROM questions WHERE subject = ? AND chapter = ? GROUP BY topic" cursor = connection.execute(query, (subject, chapter)) topics = cursor.fetchall() connection.close() if not topics: raise HTTPException(status_code=404, detail="Chapter not found or no topics available.") return { "subject": subject, "chapter": chapter, "topics": [dict(topic) for topic in topics] } @app.get("/{subject}/{chapter}/{topic}") def list_questions(subject: str, chapter: str, topic: str): """ Return list of questions for a given topic. """ connection = get_db_connection() query = "SELECT * FROM questions WHERE subject = ? AND chapter = ? AND topic = ?" cursor = connection.execute(query, (subject, chapter, topic)) questions = cursor.fetchall() connection.close() if not questions: raise HTTPException(status_code=404, detail="Topic not found or no questions available.") return { "subject": subject, "chapter": chapter, "topic": topic, "questions": [dict(question) for question in questions] } #import logging #logging.basicConfig(level=logging.DEBUG) class TestParameters(BaseModel): subjects: List[str] = None chapters: List[str] = None topics: List[str] = None num_questions: int = 10 total_time: int = 60 @app.post("/generate-test") def generate_test(test_params: TestParameters): num_questions = test_params.num_questions # Number of questions per subject total_time = test_params.total_time subjects = test_params.subjects chapters = test_params.chapters topics = test_params.topics # Connect to the database connection = get_db_connection() # Prepare the result for the test test = { "total_questions": num_questions * len(subjects), # Total number of questions = num_questions per subject * number of subjects "total_time": total_time, "questions": [] } # Fetch questions for each subject for subject in subjects: query = "SELECT * FROM questions WHERE subject = ?" params = [subject] # Apply additional filters if provided if chapters: query += " AND chapter IN ({})".format(','.join(['?'] * len(chapters))) params.extend(chapters) if topics: query += " AND topic IN ({})".format(','.join(['?'] * len(topics))) params.extend(topics) cursor = connection.execute(query, params) questions = cursor.fetchall() # If no questions found for this subject, raise an error if not questions: raise HTTPException(status_code=404, detail=f"No questions found for subject: {subject}") # If not enough questions are available for this subject, raise an error if len(questions) < num_questions: raise HTTPException( status_code=400, detail=f"Not enough questions available for subject {subject}. Requested {num_questions}, but found {len(questions)}." ) # Randomly select the requested number of questions for the subject selected_questions = random.sample(questions, num_questions) # Add the selected questions to the test test['questions'].extend([dict(q) for q in selected_questions]) connection.close() # Return the generated test return test