Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions backend/app/api/v1/endpoints/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,10 @@ async def github_callback_handler(request: Request, code: str = None, state: str
"""
# Verify state parameter to prevent CSRF attacks
stored_state = request.session.get('oauth_state')
print(f"DEBUG: Received state: {state}, Stored state: {stored_state}")
if not state or not stored_state or state != stored_state:
request.session.pop('oauth_state', None) # Clean up state
print(f"DEBUG: State mismatch - received: {state}, stored: {stored_state}")
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Invalid state parameter. CSRF check failed."
Expand Down
2 changes: 1 addition & 1 deletion backend/app/api/v1/endpoints/match.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ async def match_issues(
all_keywords.extend(topics)

# Get top matched issues
result = get_top_matched_issues(
result = await get_top_matched_issues(
query_text=text_blob,
keywords=all_keywords,
languages=languages,
Expand Down
30 changes: 16 additions & 14 deletions backend/app/main.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import os
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from starlette.middleware.sessions import SessionMiddleware
Expand All @@ -15,11 +16,11 @@
app.add_middleware(
SessionMiddleware,
secret_key=settings.SECRET_KEY,
# --- Optional Session Cookie Parameters (consider for production) ---
# session_cookie="your_app_session", # Customize the cookie name
# max_age=7 * 24 * 60 * 60, # Example: Cookie expires after 7 days
# same_site="lax", # Or "strict" for more security, but test carefully
# https_only=True, # Recommended: Send cookie only over HTTPS
# --- Session Cookie Parameters ---
session_cookie="issuematch_session", # Customize the cookie name
max_age=7 * 24 * 60 * 60, # Example: Cookie expires after 7 days
same_site="lax", # Use lax for localhost development to avoid CSRF issues
https_only=False, # No HTTPS in development
)


Expand All @@ -32,7 +33,6 @@
]

# In production, allow requests from any origin
import os
if os.environ.get("RENDER", False):
origins = ["*"] # Allow all origins in production

Expand Down Expand Up @@ -62,14 +62,16 @@ async def read_root():
# --- Optional: Startup/Shutdown Event Handlers ---
# These functions can run code when the server starts or stops.
# Useful for loading resources (like a FAISS index) or cleaning up.
# @app.on_event("startup")
# async def startup_event():
# """
# Code to run when the application starts up.
# Example: Load ML models, FAISS index, connect to databases.
# """
# print("Backend server starting up...")
# # load_faiss_index() # Example placeholder
from .services.faiss_search import load_model

@app.on_event("startup")
async def startup_event():
"""
Code to run when the application starts up.
Example: Load ML models, FAISS index, connect to databases.
"""
print("Backend server starting up...")
load_model()

# @app.on_event("shutdown")
# async def shutdown_event():
Expand Down
100 changes: 51 additions & 49 deletions backend/app/services/faiss_search.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import requests
import asyncio
import aiohttp
from sentence_transformers import SentenceTransformer
import faiss, re
import numpy as np
Expand All @@ -17,53 +18,48 @@
# Global variables
model = None

# Initialize the model
try:
logger.info(f"Loading sentence transformer model: {MODEL_NAME}")
model = SentenceTransformer(MODEL_NAME)
logger.info("Model loaded successfully")
except Exception as e:
logger.error(f"Error loading model: {str(e)}")
model = None


def fetch_github_issues(keywords: List[str], top_k: int = TOP_PER_KEYWORD, github_token: Optional[str] = None) -> List[
Dict[str, Any]]:
"""
Fetch GitHub issues based on keywords.

Args:
keywords: List of keywords to search for
top_k: Number of issues to fetch per keyword
github_token: GitHub API token for authentication
def load_model():
global model
if model is None:
try:
logger.info(f"Loading sentence transformer model: {MODEL_NAME}")
model = SentenceTransformer(MODEL_NAME)
logger.info("Model loaded successfully")
except Exception as e:
logger.error(f"Error loading model: {str(e)}")
model = None

load_model()

async def fetch_issues_for_keyword(session: aiohttp.ClientSession, keyword: str, top_k: int, headers: Dict[str, str]) -> List[Dict[str, Any]]:
query = f'{keyword}+state:open+type:issue'
url = f"https://api.github.com/search/issues?q={query}&per_page={top_k}"
logger.info(f"Fetching issues for keyword: {keyword}")
async with session.get(url, headers=headers) as response:
if response.status == 200:
json_response = await response.json()
items = json_response.get('items', [])
logger.info(f"Found {len(items)} issues for keyword: {keyword}")
return items[:top_k]
else:
logger.error(f"Error for keyword: {keyword}, Status Code: {response.status}")
if response.status == 403:
logger.error("Rate limit exceeded or authentication required")
elif response.status == 401:
logger.error("Unauthorized - check your GitHub token")
return []

Returns:
List of GitHub issues
"""
async def fetch_github_issues(keywords: List[str], top_k: int = TOP_PER_KEYWORD, github_token: Optional[str] = None) -> List[Dict[str, Any]]:
logger.info(f"Fetching GitHub issues for keywords: {keywords}")

headers = {"Accept": "application/vnd.github+json"}
if github_token:
headers["Authorization"] = f"Bearer {github_token}"

all_issues = []
for keyword in keywords:
query = f'label:"{keyword}"+state:open+type:issue'
url = f"https://api.github.com/search/issues?q={query}&per_page={top_k}"

# logger.info(f"Fetching issues for keyword: {keyword}")
response = requests.get(url, headers=headers)

if response.status_code == 200:
items = response.json().get('items', [])
# logger.info(f"Found {len(items)} issues for keyword: {keyword}")
all_issues.extend(items[:top_k]) # Take top N only
else:
logger.error(f"Error for keyword: {keyword}, Status Code: {response.status_code}")
if response.status_code == 403:
logger.error("Rate limit exceeded or authentication required")
elif response.status_code == 401:
logger.error("Unauthorized - check your GitHub token")
async with aiohttp.ClientSession() as session:
tasks = [fetch_issues_for_keyword(session, keyword, top_k, headers) for keyword in keywords]
results = await asyncio.gather(*tasks)

all_issues = [issue for sublist in results for issue in sublist]

# Deduplicate by URL
unique_issues = list({issue['html_url']: issue for issue in all_issues}.values())
Expand Down Expand Up @@ -176,7 +172,7 @@ def format_issues_json(issues: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
return results


def get_top_matched_issues(
async def get_top_matched_issues(
query_text: str,
keywords: List[str],
languages: List[str] = None,
Expand All @@ -199,7 +195,7 @@ def get_top_matched_issues(
global model

try:
# logger.info(f"Getting top matched issues for query: {query_text[:100]}...")
logger.info(f"Getting top matched issues for query: {query_text[:100]}...")

# Check if model is loaded
if model is None:
Expand All @@ -221,16 +217,22 @@ def get_top_matched_issues(
search_keywords = list(set(search_keywords))
logger.info(f"Search keywords: {search_keywords}")

# Fetch issues
issues = fetch_github_issues(search_keywords, top_k=TOP_PER_KEYWORD, github_token=github_token)
# Fetch issues asynchronously
issues = await fetch_github_issues(search_keywords, top_k=TOP_PER_KEYWORD, github_token=github_token)

# Fallback: If no issues found with specific keywords, try with general programming keywords
if not issues:
logger.warning("No issues fetched")
logger.warning("No issues fetched with specific keywords, trying fallback keywords")
fallback_keywords = ["python", "javascript", "java", "react", "node", "good first issue"]
issues = await fetch_github_issues(fallback_keywords, top_k=TOP_PER_KEYWORD, github_token=github_token)

if not issues:
logger.warning("No issues fetched even with fallback keywords")
return {
"recommendations": [],
"issues_fetched": 0,
"issues_indexed": 0,
"message": "No issues found for the given keywords"
"message": "No issues found for the given keywords. Try adjusting your search criteria or check back later."
}

# Prepare issue texts for embedding
Expand Down Expand Up @@ -264,4 +266,4 @@ def get_top_matched_issues(
"issues_fetched": 0,
"issues_indexed": 0,
"message": f"Error matching issues: {str(e)}"
}
}
1 change: 1 addition & 0 deletions backend/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ beautifulsoup4==4.13.0
requests
numpy
vertexai
aiohttp==3.9.1
21 changes: 17 additions & 4 deletions frontend/app/match/page.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
"use client"

import { useState } from "react"
import { Slider } from "@/components/slider"
import { IssuesList } from "@/components/issues-list"
import { SortDropdown } from "@/components/sort-dropdown"
import { SortOptions } from "@/components/sort-options"
import { InspirationSidebar } from "@/components/inspiration-sidebar"
import { NavigationButtons } from "@/components/navigation-buttons"
import { Footer } from "@/components/footer"
import { IssueFilter } from "@/components/issue-filter"

export default function Home() {
const [filters, setFilters] = useState<{ keywords: string[] }>({
keywords: [],
})

return (
<main className="min-h-screen bg-gray-50 dark:bg-[#0d1117] text-gray-900 dark:text-white">
{/* Hero Slider Section */}
Expand All @@ -28,11 +36,16 @@ export default function Home() {
{/*</div>*/}

<div className="flex-1">
<div className="flex justify-between items-center mb-6">
<h2 className="text-2xl font-bold text-gray-900 dark:text-white">Recommended</h2>
<SortDropdown />
<div className="flex flex-col gap-4 mb-6">
<div className="flex justify-between items-center">
<h2 className="text-2xl font-bold text-gray-900 dark:text-white">Recommended</h2>
<div className="flex items-center gap-3">
<IssueFilter onFilterChange={setFilters} />
<SortDropdown />
</div>
</div>
</div>
<IssuesList type="recommended" />
<IssuesList type="recommended" filters={filters} />
</div>

<div className="w-full lg:w-80 shrink-0 hidden lg:block">
Expand Down
4 changes: 2 additions & 2 deletions frontend/app/skills/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ export default function SkillsPage() {
// Loading state - show while checking auth or loading user data
if (loading || authLoading) {
return (
<div className="min-h-screen bg-gradient-to-br from-gray-900 to-purple-900 text-white flex items-center justify-center">
<div className="min-h-screen bg-gradient-to-br from-gray-50 to-blue-50 dark:from-gray-900 dark:to-purple-900 text-gray-900 dark:text-white flex items-center justify-center">
<div className="text-center">
<h2 className="text-2xl font-bold mb-4">Loading...</h2>
<Progress value={100} className="w-[300px]" />
Expand All @@ -146,7 +146,7 @@ export default function SkillsPage() {
}

return (
<div className="min-h-screen bg-gradient-to-br from-gray-900 to-purple-900 text-white">
<div className="min-h-screen bg-gradient-to-br from-gray-50 to-blue-50 dark:from-gray-900 dark:to-purple-900 text-gray-900 dark:text-white">
{currentScreen === "welcome" && <WelcomeScreen />}

{currentScreen === "test" && <SkillsTest onComplete={handleTestComplete} />}
Expand Down
Loading