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
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
38 changes: 24 additions & 14 deletions backend/app/services/faiss_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,18 @@
# 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 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()

def fetch_github_issues(keywords: List[str], top_k: int = TOP_PER_KEYWORD, github_token: Optional[str] = None) -> List[
Dict[str, Any]]:
Expand All @@ -48,15 +51,16 @@ def fetch_github_issues(keywords: List[str], top_k: int = TOP_PER_KEYWORD, githu

all_issues = []
for keyword in keywords:
query = f'label:"{keyword}"+state:open+type:issue'
# Search for keyword in title, body, and comments instead of just labels
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}")
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}")
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}")
Expand Down Expand Up @@ -199,7 +203,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 @@ -224,13 +228,19 @@ def get_top_matched_issues(
# Fetch issues
issues = 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 with specific keywords, trying fallback keywords")
fallback_keywords = ["python", "javascript", "java", "react", "node", "good first issue"]
issues = fetch_github_issues(fallback_keywords, top_k=TOP_PER_KEYWORD, github_token=github_token)

if not issues:
logger.warning("No issues fetched")
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
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
112 changes: 112 additions & 0 deletions frontend/components/issue-filter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
"use client"

import { useState, useEffect } from "react"
import { useAuth } from "@/context/auth-context"
import { getUserByGithubId } from "@/lib/firebase-utils"
import { Button } from "@/components/ui/button"
import { Checkbox } from "@/components/ui/checkbox"
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
import { Badge } from "@/components/ui/badge"
import { Filter } from "lucide-react"

interface IssueFilterProps {
onFilterChange: (filters: { keywords: string[] }) => void
}

const PREDEFINED_SKILLS = [
"JavaScript", "TypeScript", "Python", "Java", "C++", "C#", "Go", "Rust",
"React", "Vue", "Angular", "Node.js", "Express", "Django", "Flask", "Spring",
"Machine Learning", "Data Science", "AI", "Web Development", "Mobile Development",
"DevOps", "Cloud Computing", "Database", "API", "Testing"
]

export function IssueFilter({ onFilterChange }: IssueFilterProps) {
const [selectedKeywords, setSelectedKeywords] = useState<string[]>([])
const [userSkills, setUserSkills] = useState<string[]>([])
const [isOpen, setIsOpen] = useState(false)
const { user } = useAuth()

// Fetch user skills
useEffect(() => {
const fetchUserSkills = async () => {
if (!user) return
try {
const result = await getUserByGithubId(user.id)
if (result.success && result.data?.skill_keywords) {
setUserSkills(result.data.skill_keywords)
}
} catch (error) {
console.error("Error fetching user skills:", error)
}
}
fetchUserSkills()
}, [user])

// Combine user skills with predefined skills
const allSkills = Array.from(new Set([...userSkills, ...PREDEFINED_SKILLS]))

const handleSkillToggle = (skill: string) => {
const updated = selectedKeywords.includes(skill)
? selectedKeywords.filter(s => s !== skill)
: [...selectedKeywords, skill]
setSelectedKeywords(updated)
}

const applyFilters = () => {
onFilterChange({
keywords: selectedKeywords
})
setIsOpen(false)
}

const clearFilters = () => {
setSelectedKeywords([])
onFilterChange({ keywords: [] })
}

const totalSelected = selectedKeywords.length

return (
<Popover open={isOpen} onOpenChange={setIsOpen}>
<PopoverTrigger asChild>
<Button variant="outline" className="flex items-center gap-2">
<Filter className="h-4 w-4" />
Filter Issues
{totalSelected > 0 && (
<Badge variant="secondary" className="ml-1">
{totalSelected}
</Badge>
)}
</Button>
</PopoverTrigger>
<PopoverContent className="w-80 p-4">
<div className="space-y-4">
<div>
<h4 className="font-medium mb-2">Keywords</h4>
<div className="grid grid-cols-2 gap-2 max-h-32 overflow-y-auto">
{allSkills.map(skill => (
<div key={`keyword-${skill}`} className="flex items-center space-x-2">
<Checkbox
id={`keyword-${skill}`}
checked={selectedKeywords.includes(skill)}
onCheckedChange={() => handleSkillToggle(skill)}
/>
<label htmlFor={`keyword-${skill}`} className="text-sm">{skill}</label>
</div>
))}
</div>
</div>

<div className="flex gap-2 pt-2 border-t">
<Button onClick={applyFilters} size="sm">
Apply Filters
</Button>
<Button onClick={clearFilters} variant="outline" size="sm">
Clear All
</Button>
</div>
</div>
</PopoverContent>
</Popover>
)
}
Loading