From 84ab54feaf8bdb421f606955263574d06bdebcae Mon Sep 17 00:00:00 2001 From: durgeshninave9 Date: Tue, 7 Oct 2025 22:23:52 +0530 Subject: [PATCH 1/3] Added endpoints for list & get questions and additional fields --- app/api/routes/questions.py | 68 ++++++++++++++++- app/prompts/daily_question_prompt.py | 1 + app/schemas/utils.py | 25 ++++++- app/services/questions.py | 105 +++++++++++++++++++++++++++ 4 files changed, 195 insertions(+), 4 deletions(-) diff --git a/app/api/routes/questions.py b/app/api/routes/questions.py index e91e5b6..86eabb8 100644 --- a/app/api/routes/questions.py +++ b/app/api/routes/questions.py @@ -1,10 +1,12 @@ import json +from typing import List -from fastapi import APIRouter +from fastapi import APIRouter, Depends, Request -from app.schemas.utils import TechDescription, UserPrompt +from app.schemas.utils import (QuestionFilters, TechDescription, UserPrompt) from app.services.questions import (add_daily_question_to_mongodb, - add_tech_description_to_store) + add_tech_description_to_store, + get_question_by_id, list_questions) from app.services.questions import \ generate_daily_question as generate_daily_question_service from app.services.questions import get_most_recent_daily_question @@ -48,3 +50,63 @@ def get_daily_question(): if recent_question: return recent_question return {"message": "No daily question found."} + + +@router.get("/list_questions") +async def list_questions( + filters: QuestionFilters = Depends(), + request: Request = Depends() +): + """ + Retrieve all questions with filtering and pagination support. + + Parameters: + filters: Query parameters for filtering, sorting and pagination + - page: Current page number (starts from 1) + - per_page: Number of items per page (10-100) + - title: Filter by title (case-insensitive partial match) + - tags: Filter by list of tags + - difficulty: Filter by difficulty level + - status: Filter by status + - description: Filter by description (case-insensitive partial match) + - sort_by: Field to sort by (default: created_at) + - sort_order: Sort order (asc/desc) + """ + result = list_questions( + page=filters.page, + per_page=filters.per_page, + title=filters.title, + tags=filters.tags, + difficulty=filters.difficulty, + status=filters.status, + description=filters.description, + sort_by=filters.sort_by, + sort_order=filters.sort_order + ) + + # Generate next page URL if available + total_pages = result["total_pages"] + current_page = filters.page + + if current_page < total_pages: + base_url = str(request.base_url) + query_params = dict(request.query_params) + query_params["page"] = str(current_page + 1) + result["next_page"] = f"{base_url}list_questions?{'&'.join(f'{k}={v}' for k, v in query_params.items())}" + else: + result["next_page"] = None + return result + + +@router.get("/get_question/{question_id}") +def get_question(question_id: str): + """ + Retrieve a specific question by ID. + + Parameters: + question_id: The unique identifier of the question + """ + question = get_question_by_id(question_id) + if question: + return question + return {"message": "Question not found"} \ No newline at end of file diff --git a/app/prompts/daily_question_prompt.py b/app/prompts/daily_question_prompt.py index 512c69e..4c2747f 100644 --- a/app/prompts/daily_question_prompt.py +++ b/app/prompts/daily_question_prompt.py @@ -44,5 +44,6 @@ "difficulty": "easy|medium|hard", // Based on required knowledge depth and complexity "tags": ["string"], // 3-6 relevant technical tags for categorization "learning_objectives": ["string"] // 2-3 key skills/concepts the question teaches + "categories": ["string"] // 1-2 broader categories the question falls under (e.g., "Web Development", "Data Structures") }} """ diff --git a/app/schemas/utils.py b/app/schemas/utils.py index 5059453..4e36562 100644 --- a/app/schemas/utils.py +++ b/app/schemas/utils.py @@ -1,4 +1,6 @@ -from pydantic import BaseModel +from enum import Enum +from typing import List, Optional +from pydantic import BaseModel, Field class UserPrompt(BaseModel): @@ -21,3 +23,24 @@ class TechDescription(BaseModel): description: str metadata: TechDescriptionMetadata + + +class SortOrder(str, Enum): + """Sort order enum.""" + ASC = "asc" + DESC = "desc" + + +class QuestionFilters(BaseModel): + """Schema for question list filters.""" + title: Optional[str] = None + tags: Optional[List[str]] = None + difficulty: Optional[str] = None + status: Optional[str] = None + description: Optional[str] = None + skills: Optional[List[str]] = None + categories: Optional[List[str]] = None + page: int = Field(default=1, gt=0) + per_page: int = Field(default=10, gt=0, le=100) + sort_by: str = Field(default="created_at") + sort_order: SortOrder = SortOrder.DESC diff --git a/app/services/questions.py b/app/services/questions.py index 7c5c744..9965597 100644 --- a/app/services/questions.py +++ b/app/services/questions.py @@ -34,6 +34,8 @@ def add_daily_question_to_mongodb(question: dict) -> None: "tags": question.get("tags", []), "difficulty": question.get("difficulty", "None"), "status": "published", + "skills": question.get("learning_objectives", []), + "categories": question.get("categories", []), } questions_collection.insert_one(question_document) @@ -62,5 +64,108 @@ def get_most_recent_daily_question(): "difficulty": recent_question["difficulty"], "status": recent_question["status"], "hints": recent_question.get("hints", []), + "skills": recent_question.get("learning_objectives", []), + "categories": recent_question.get("categories", []), + } + return None + + +def list_questions( + page: int = 1, + per_page: int = 10, + title: str = None, + tags: list = None, + difficulty: str = None, + status: str = None, + description: str = None, + skills: list = None, + categories: list = None, + sort_by: str = "created_at", + sort_order: str = "desc" +) -> dict: + """ + List questions with filtering and pagination support. + """ + # Build the filter query + query = {} + if title: + query["title"] = {"$regex": title, "$options": "i"} + if tags: + query["tags"] = {"$in": tags} + if difficulty: + query["difficulty"] = difficulty + if status: + query["status"] = status + if skills: + query["learning_objectives"] = {"$in": skills} + if categories: + query["categories"] = {"$in": categories} + if description: + query["description"] = {"$regex": description, "$options": "i"} + + # Calculate skip and limit for pagination + skip = (page - 1) * per_page + + # Set sort order + sort_direction = -1 if sort_order.lower() == "desc" else 1 + sort_criteria = [(sort_by, sort_direction)] + + # Get total count for pagination + total_questions = questions_collection.count_documents(query) + + # Fetch questions with pagination + questions = questions_collection.find( + query, + skip=skip, + limit=per_page, + sort=sort_criteria + ) + + # Convert cursor to list and format response + questions_list = [] + for q in questions: + questions_list.append({ + "id": q["_id"], + "title": q["title"], + "description": q["description"], + "created_at": q["created_at"], + "likes": q["likes"], + "dislikes": q["dislikes"], + "tags": q["tags"], + "difficulty": q["difficulty"], + "status": q["status"], + "hints": q.get("hints", []), + "skills": q.get("learning_objectives", []), + "categories": q.get("categories", []) + }) + + return { + "questions": questions_list, + "total": total_questions, + "page": page, + "per_page": per_page, + "total_pages": (total_questions + per_page - 1) // per_page + } + + +def get_question_by_id(question_id: str) -> dict: + """ + Get a specific question by ID. + """ + question = questions_collection.find_one({"_id": question_id}) + if question: + return { + "id": question["_id"], + "title": question["title"], + "description": question["description"], + "created_at": question["created_at"], + "likes": question["likes"], + "dislikes": question["dislikes"], + "tags": question["tags"], + "difficulty": question["difficulty"], + "status": question["status"], + "hints": question.get("hints", []), + "skills": question.get("learning_objectives", []), + "categories": question.get("categories", []) } return None From 7617b39bfe425c1793005454ef53551a497cc99c Mon Sep 17 00:00:00 2001 From: durgeshninave9 Date: Tue, 7 Oct 2025 22:31:26 +0530 Subject: [PATCH 2/3] Fix isort issue --- app/api/routes/questions.py | 8 ++++---- app/schemas/utils.py | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/api/routes/questions.py b/app/api/routes/questions.py index 86eabb8..0546f14 100644 --- a/app/api/routes/questions.py +++ b/app/api/routes/questions.py @@ -3,13 +3,13 @@ from fastapi import APIRouter, Depends, Request -from app.schemas.utils import (QuestionFilters, TechDescription, UserPrompt) +from app.schemas.utils import QuestionFilters, TechDescription, UserPrompt from app.services.questions import (add_daily_question_to_mongodb, - add_tech_description_to_store, - get_question_by_id, list_questions) + add_tech_description_to_store) from app.services.questions import \ generate_daily_question as generate_daily_question_service -from app.services.questions import get_most_recent_daily_question +from app.services.questions import (get_most_recent_daily_question, + get_question_by_id, list_questions) router = APIRouter() diff --git a/app/schemas/utils.py b/app/schemas/utils.py index 4e36562..ed7aa59 100644 --- a/app/schemas/utils.py +++ b/app/schemas/utils.py @@ -1,5 +1,6 @@ from enum import Enum from typing import List, Optional + from pydantic import BaseModel, Field From c0a25d5f56c00e87e716f4309b980a631862c953 Mon Sep 17 00:00:00 2001 From: durgeshninave9 Date: Sat, 11 Oct 2025 18:24:11 +0530 Subject: [PATCH 3/3] Fix interval issue and update list question function --- app/api/routes/questions.py | 57 ++++++++++++++++++++++--------------- app/scheduler.py | 2 +- 2 files changed, 35 insertions(+), 24 deletions(-) diff --git a/app/api/routes/questions.py b/app/api/routes/questions.py index 0546f14..c5596fd 100644 --- a/app/api/routes/questions.py +++ b/app/api/routes/questions.py @@ -53,40 +53,50 @@ def get_daily_question(): @router.get("/list_questions") -async def list_questions( - filters: QuestionFilters = Depends(), - request: Request = Depends() +async def list_questions_endpoint( + request: Request, + page: int = 1, + per_page: int = 10, + title: str | None = None, + tags: str | None = None, + difficulty: str | None = None, + status: str | None = None, + description: str | None = None, + sort_by: str = "created_at", + sort_order: str = "desc" ): """ Retrieve all questions with filtering and pagination support. Parameters: - filters: Query parameters for filtering, sorting and pagination - - page: Current page number (starts from 1) - - per_page: Number of items per page (10-100) - - title: Filter by title (case-insensitive partial match) - - tags: Filter by list of tags - - difficulty: Filter by difficulty level - - status: Filter by status - - description: Filter by description (case-insensitive partial match) - - sort_by: Field to sort by (default: created_at) - - sort_order: Sort order (asc/desc) + page: Current page number (starts from 1) + per_page: Number of items per page (10-100) + title: Filter by title (case-insensitive partial match) + tags: Comma-separated list of tags to filter by + difficulty: Filter by difficulty level + status: Filter by status + description: Filter by description (case-insensitive partial match) + sort_by: Field to sort by (default: created_at) + sort_order: Sort order (asc/desc) """ + # Convert comma-separated tags string to list if provided + tag_list = tags.split(",") if tags else None + result = list_questions( - page=filters.page, - per_page=filters.per_page, - title=filters.title, - tags=filters.tags, - difficulty=filters.difficulty, - status=filters.status, - description=filters.description, - sort_by=filters.sort_by, - sort_order=filters.sort_order + page=page, + per_page=per_page, + title=title, + tags=tag_list, + difficulty=difficulty, + status=status, + description=description, + sort_by=sort_by, + sort_order=sort_order ) # Generate next page URL if available total_pages = result["total_pages"] - current_page = filters.page + current_page = page if current_page < total_pages: base_url = str(request.base_url) @@ -95,6 +105,7 @@ async def list_questions( result["next_page"] = f"{base_url}list_questions?{'&'.join(f'{k}={v}' for k, v in query_params.items())}" else: result["next_page"] = None + return result diff --git a/app/scheduler.py b/app/scheduler.py index b62c645..cc4d715 100644 --- a/app/scheduler.py +++ b/app/scheduler.py @@ -41,7 +41,7 @@ def start(self, interval_hour: int): self.job = self.scheduler.add_job( self._daily_question_task, "interval", - hour=24, + hours=interval_hour, id="daily_question_job", replace_existing=True, )