-
Notifications
You must be signed in to change notification settings - Fork 58
feat: added fastapi-mongo sample #93
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,4 @@ | ||||||||
| **/__pycache__/** | ||||||||
| **/__init__.py/** | ||||||||
| __init__.py | ||||||||
|
Comment on lines
+2
to
+3
|
||||||||
| **/__init__.py/** | |
| __init__.py |
Copilot
AI
Jan 21, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing environment file in .gitignore. The application uses a .env file for configuration (as seen in core/config.py), but .env is not listed in .gitignore. This could lead to accidental commits of sensitive credentials like SECRET_KEY, MONGODB_URI, and API keys.
| .DS_Store | |
| .DS_Store | |
| .env |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,32 @@ | ||||||
| # 1. Use a lightweight Python 3.10 image | ||||||
| FROM python:3.10-slim | ||||||
|
|
||||||
| # 2. Set environment variables | ||||||
| ENV PYTHONDONTWRITEBYTECODE=1 | ||||||
| ENV PYTHONUNBUFFERED=1 | ||||||
|
|
||||||
| # 3. Set working directory | ||||||
| WORKDIR /app | ||||||
|
|
||||||
| # 4. Install system dependencies | ||||||
| RUN apt-get update && apt-get install -y --no-install-recommends \ | ||||||
| build-essential \ | ||||||
| gcc \ | ||||||
| libpq-dev \ | ||||||
| curl \ | ||||||
| && rm -rf /var/lib/apt/lists/* | ||||||
|
|
||||||
| # 5. Copy requirements.txt from the root folder | ||||||
| COPY ../requirements.txt . | ||||||
|
||||||
| COPY ../requirements.txt . | |
| COPY requirements.txt . |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,20 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from jose import jwt | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from datetime import datetime, timedelta | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from dotenv import load_dotenv | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import os | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| load_dotenv() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| SECRET_KEY = os.getenv("SECRET_KEY") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| SECRET_KEY = os.getenv("SECRET_KEY") | |
| SECRET_KEY = os.getenv("SECRET_KEY") | |
| if not SECRET_KEY: | |
| raise RuntimeError("SECRET_KEY environment variable must be set and non-empty") |
Copilot
AI
Jan 21, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The datetime.utcnow() method is deprecated as of Python 3.12. Replace with datetime.now(timezone.utc) for timezone-aware datetime handling.
Copilot
AI
Jan 21, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file appears to be duplicate JWT handling code. The utils/jwt.py file already provides JWT token creation and verification functionality. Having two implementations of the same functionality can lead to inconsistencies and maintenance issues. Consider removing this file and using the utils/jwt.py implementation throughout the application.
| from jose import jwt | |
| from datetime import datetime, timedelta | |
| from dotenv import load_dotenv | |
| import os | |
| load_dotenv() | |
| SECRET_KEY = os.getenv("SECRET_KEY") | |
| ALGORITHM = "HS256" | |
| def create_access_token(data: dict, expires_delta: timedelta = timedelta(days=1)): | |
| to_encode = data.copy() | |
| expire = datetime.utcnow() + expires_delta | |
| to_encode.update({"exp": expire}) | |
| return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) | |
| def decode_access_token(token: str): | |
| return jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) | |
| from datetime import timedelta | |
| from utils.jwt import ( | |
| create_access_token as _create_access_token, | |
| decode_access_token as _decode_access_token, | |
| ) | |
| def create_access_token(data: dict, expires_delta: timedelta = timedelta(days=1)): | |
| """ | |
| Delegate access token creation to the shared utils.jwt implementation. | |
| """ | |
| return _create_access_token(data, expires_delta) | |
| def decode_access_token(token: str): | |
| """ | |
| Delegate access token decoding to the shared utils.jwt implementation. | |
| """ | |
| return _decode_access_token(token) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| # app/core/config.py | ||
| from pydantic_settings import BaseSettings | ||
|
|
||
|
|
||
| class Settings(BaseSettings): | ||
| MONGODB_URI: str | ||
| SECRET_KEY: str | ||
| FRONTEND_URL: str | ||
| GEMINI_API_KEY: str | ||
| YOUTUBE_API_KEY: str | ||
| CORS_ORIGINS: str | ||
|
|
||
| class Config: | ||
| env_file = ".env" | ||
|
|
||
|
|
||
| settings = Settings() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| # app/db/mongodb.py | ||
| from motor.motor_asyncio import AsyncIOMotorClient | ||
| from quizzly.core.config import settings | ||
|
|
||
| client = AsyncIOMotorClient(settings.MONGODB_URI) | ||
| db = client.quiz_app |
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. could you please move the relevant content from this folder to the new readme file to be created at the project root ?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @AkashKumar7902 Under this root README?
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| ### Keploy API Test Generator for FastAPI with MongoDB | ||
|
|
||
| This project demonstrates how to use Keploy to automatically generate API tests for a FastAPI application that interacts with a MongoDB database. Keploy captures the API requests and responses, allowing you to create comprehensive test cases effortlessly. | ||
|
|
||
| Keploy API Test Generator: https://keploy.io/docs/running-keploy/api-test-generator/ | ||
|
|
||
| ### About the Project | ||
|
|
||
| This is FastAPI + MongoDB backend written for a Quiz Application. The application allows users to perform CRUD operations on quiz data stored in a MongoDB database. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| # app/main.py | ||
| from fastapi import FastAPI | ||
| from quizzly.routes import parent, quiz, content | ||
| from fastapi.middleware.cors import CORSMiddleware | ||
| from fastapi.middleware.cors import CORSMiddleware | ||
| from quizzly.routes import parent, quiz | ||
syedali237 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| from quizzly.core.config import settings | ||
|
|
||
|
|
||
| app = FastAPI() | ||
| # Read and parse CORS origins | ||
| origins_str = settings.CORS_ORIGINS | ||
| origins = [origin.strip() for origin in origins_str.split(",") if origin.strip()] | ||
|
|
||
| print(origins) | ||
|
|
||
syedali237 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| # Enable CORS | ||
| app.add_middleware( | ||
| CORSMiddleware, | ||
| allow_origins=origins, | ||
| allow_credentials=True, | ||
| allow_methods=["*"], | ||
| allow_headers=["*"], | ||
| ) | ||
|
|
||
| app.include_router(parent.router, prefix="/parent", tags=["Parent"]) | ||
| app.include_router(quiz.router, prefix="/quiz", tags=["Quiz"]) | ||
| app.include_router(content.router, prefix="/content", tags=["Content"]) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| # app/models/parent.py | ||
| from pydantic import BaseModel, Field | ||
| from typing import Optional | ||
| from bson import ObjectId | ||
|
|
||
|
|
||
| class ParentCreate(BaseModel): | ||
| username: str | ||
| password: str | ||
| name: Optional[str] | ||
|
|
||
|
|
||
| class Parent(BaseModel): | ||
| id: Optional[str] = Field(default_factory=lambda: str(ObjectId()), alias="_id") | ||
| name: str | ||
| username: str | ||
| password: str |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,82 @@ | ||||||
| # app/models/quiz.py | ||||||
|
|
||||||
| from pydantic import BaseModel, Field, HttpUrl | ||||||
| from typing import List, Optional | ||||||
| from bson import ObjectId | ||||||
| from datetime import datetime | ||||||
| import pytz | ||||||
|
|
||||||
| IST = pytz.timezone("Asia/Kolkata") | ||||||
|
|
||||||
|
|
||||||
| class EndQuizRequest(BaseModel): | ||||||
| name: str | ||||||
|
|
||||||
|
|
||||||
| class QuizStart(BaseModel): | ||||||
| password: int | ||||||
| name: str | ||||||
|
|
||||||
|
|
||||||
| class AnswerSubmission(BaseModel): | ||||||
| name: str | ||||||
| answers: List[str] | ||||||
|
|
||||||
|
|
||||||
| # Question model to represent individual question details | ||||||
| class Question(BaseModel): | ||||||
| question: str | ||||||
| choice_A: str | ||||||
| choice_B: str | ||||||
| choice_C: str | ||||||
| choice_D: str | ||||||
| answer: str # Should be one of "A", "B", "C", or "D" | ||||||
|
||||||
| is_correct: Optional[bool] = False | ||||||
|
|
||||||
|
|
||||||
| # QuizCreate model – input from client | ||||||
| class QuizCreate(BaseModel): | ||||||
| name: str | ||||||
| subject: str | ||||||
| num_questions: int | ||||||
| # trigger_link: HttpUrl | ||||||
| # questions: List[Question] | ||||||
| topic: str | ||||||
| difficulty_level: int | ||||||
|
||||||
|
|
||||||
|
|
||||||
| class QuestionUpdate(BaseModel): | ||||||
| question: str | ||||||
| choice_A: str | ||||||
| choice_B: str | ||||||
| choice_C: str | ||||||
| choice_D: str | ||||||
| answer: str | ||||||
| is_correct: bool | ||||||
|
|
||||||
|
|
||||||
| # Main Quiz model – includes fields auto-populated by backend | ||||||
| class Quiz(BaseModel): | ||||||
| id: str = Field(default_factory=lambda: str(ObjectId()), alias="_id") | ||||||
| name: str | ||||||
| subject: str | ||||||
| created_by: str | ||||||
| created_at: datetime = Field(default_factory=datetime.now(IST)) | ||||||
|
||||||
| created_at: datetime = Field(default_factory=datetime.now(IST)) | |
| created_at: datetime = Field(default_factory=lambda: datetime.now(IST)) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| from fastapi import APIRouter, HTTPException | ||
| from typing import List | ||
| import json | ||
syedali237 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| from googleapiclient.discovery import build | ||
| from duckduckgo_search import DDGS | ||
| from quizzly.core.config import settings | ||
| import os | ||
syedali237 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| from pydantic import BaseModel | ||
|
|
||
| router = APIRouter() | ||
|
|
||
| class TopicsRequest(BaseModel): | ||
| topics: List[str] | ||
| num_results: int = 5 | ||
|
|
||
| @router.post("/articles") | ||
| async def get_articles(request: TopicsRequest): | ||
| """ | ||
| Get articles based on provided topics using DuckDuckGo search | ||
| """ | ||
| try: | ||
| results = {} | ||
| with DDGS() as ddgs: | ||
| for topic in request.topics: | ||
| search_results = ddgs.text(topic, max_results=request.num_results) | ||
| articles = [] | ||
| for res in search_results: | ||
| title = res.get('title') | ||
| url = res.get('href') | ||
| if title and url: | ||
| articles.append({'title': title, 'url': url}) | ||
| results[topic] = articles | ||
| return results | ||
| except Exception as e: | ||
| raise HTTPException(status_code=500, detail=str(e)) | ||
|
|
||
| @router.post("/youtube") | ||
| async def get_youtube_videos(request: TopicsRequest): | ||
| """ | ||
| Get YouTube videos with thumbnails based on provided topics | ||
| """ | ||
| if not settings.YOUTUBE_API_KEY: | ||
| raise HTTPException(status_code=500, detail="YouTube API key not configured") | ||
|
|
||
| try: | ||
| youtube = build('youtube', 'v3', developerKey=settings.YOUTUBE_API_KEY) | ||
| results = {} | ||
|
|
||
| for topic in request.topics: | ||
| search_response = youtube.search().list( | ||
| q=topic, | ||
| part='snippet', | ||
| type='video', | ||
| maxResults=request.num_results | ||
| ).execute() | ||
|
|
||
| youtube_data = [] | ||
| for item in search_response['items']: | ||
| video_title = item['snippet']['title'] | ||
| video_url = f"https://www.youtube.com/watch?v={item['id']['videoId']}" | ||
| video_id = item['id']['videoId'] | ||
syedali237 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| thumbnail_url = item['snippet']['thumbnails']['high']['url'] | ||
| youtube_data.append({ | ||
| 'title': video_title, | ||
| 'video_url': video_url, | ||
| 'thumbnail_url': thumbnail_url | ||
| }) | ||
|
|
||
| results[topic] = youtube_data | ||
|
|
||
| return results | ||
| except Exception as e: | ||
| raise HTTPException(status_code=500, detail=str(e)) | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,36 @@ | ||||||||||||||||
| # app/routes/parent.py | ||||||||||||||||
| import bcrypt | ||||||||||||||||
| from fastapi import APIRouter, HTTPException | ||||||||||||||||
| from quizzly.models.parent import Parent, ParentCreate | ||||||||||||||||
| from quizzly.db.mongodb import db | ||||||||||||||||
| from quizzly.utils.jwt import create_token | ||||||||||||||||
| from pydantic import BaseModel | ||||||||||||||||
|
|
||||||||||||||||
|
|
||||||||||||||||
| class LoginRequest(BaseModel): | ||||||||||||||||
| username: str | ||||||||||||||||
| password: str | ||||||||||||||||
|
|
||||||||||||||||
|
|
||||||||||||||||
| router = APIRouter() | ||||||||||||||||
|
|
||||||||||||||||
|
|
||||||||||||||||
| @router.post("/register") | ||||||||||||||||
| async def register_parent(data: ParentCreate): | ||||||||||||||||
| parent_data = Parent(**data.dict()) | ||||||||||||||||
|
||||||||||||||||
| parent_data = Parent(**data.dict()) | |
| parent_data = Parent(**data.dict()) | |
| # Check for existing user with the same username to provide a clear error | |
| existing_parent = await db.parents.find_one({"username": parent_data.username}) | |
| if existing_parent: | |
| raise HTTPException(status_code=400, detail="Username already exists") |
Copilot
AI
Jan 21, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing input validation for the password field. The password is hashed but there's no validation for minimum length, complexity, or other security requirements before registration.

Uh oh!
There was an error while loading. Please reload this page.