interviewbot / backend /routers /profile.py
sajith-0701's picture
v5
15145f6
from fastapi import APIRouter, Depends, HTTPException
from auth.jwt import get_current_user
from database import get_db
from models.collections import USERS, RESUMES, SKILLS
from utils.helpers import str_objectid
from utils.skills import normalize_skill_list, cluster_skills
from bson import ObjectId
from services.job_description_service import (
create_job_description,
list_my_job_descriptions,
update_my_job_description,
delete_my_job_description,
parse_jd_from_file,
)
from services.group_test_service import (
list_group_tests,
get_group_test,
start_group_test_attempt,
get_group_test_result,
link_topic_session,
get_my_group_test_results,
get_my_group_test_attempt,
)
from fastapi import UploadFile, File
router = APIRouter()
@router.get("")
async def get_profile(current_user: dict = Depends(get_current_user)):
"""Get current user's profile with skills and resume info."""
db = get_db()
user = await db[USERS].find_one({"_id": ObjectId(current_user["user_id"])})
if not user:
# Fallback: try finding by email
user = await db[USERS].find_one({"email": current_user["email"]})
profile = {
"user_id": current_user["user_id"],
"name": current_user.get("name", ""),
"email": current_user.get("email", ""),
"role": current_user.get("role", "student"),
"speech_settings": {
"voice_gender": (user or {}).get("speech_settings", {}).get("voice_gender", "female"),
},
"reg_no": (user or {}).get("reg_no") or None,
}
# Get resume info
resume = await db[RESUMES].find_one({"user_id": current_user["user_id"]})
if resume:
profile["resume"] = {
"filename": resume.get("original_filename", ""),
"uploaded_at": resume.get("uploaded_at", ""),
"parsed_text": resume.get("parsed_text", ""),
"parsed_data": resume.get("parsed_data", {}),
}
else:
profile["resume"] = None
# Get skills
skills_doc = await db[SKILLS].find_one({"user_id": current_user["user_id"]})
profile["skills"] = skills_doc.get("skills", []) if skills_doc else []
profile["clustered_skills"] = cluster_skills(profile["skills"])
return profile
@router.put("/speech-settings")
async def update_speech_settings(
request_data: dict,
current_user: dict = Depends(get_current_user),
):
"""Update user's speech assistant preferences."""
db = get_db()
voice_gender = (request_data.get("voice_gender") or "female").strip().lower()
if voice_gender not in {"male", "female", "auto"}:
raise HTTPException(status_code=400, detail="voice_gender must be one of: male, female, auto")
await db[USERS].update_one(
{"_id": ObjectId(current_user["user_id"])},
{"$set": {"speech_settings.voice_gender": voice_gender}},
)
return {
"message": "Speech settings updated successfully",
"speech_settings": {"voice_gender": voice_gender},
}
@router.put("/skills")
async def update_user_skills(
request_data: dict, # Or use UpdateSkillsRequest if imported
current_user: dict = Depends(get_current_user)
):
"""Update the current user's extracted skills."""
db = get_db()
skills = normalize_skill_list(request_data.get("skills", []))
# Upsert the skills document for this user
await db[SKILLS].update_one(
{"user_id": current_user["user_id"]},
{"$set": {"skills": skills, "user_id": current_user["user_id"]}},
upsert=True
)
return {"message": "Skills updated successfully", "skills": skills}
@router.put("/resume-data")
async def update_resume_data(
request_data: dict,
current_user: dict = Depends(get_current_user)
):
"""Update the detailed parsed data of the user's resume."""
db = get_db()
parsed_data = request_data.get("parsed_data", {})
# Update only the parsed_data property inside the RESUMES collection
result = await db[RESUMES].update_one(
{"user_id": current_user["user_id"]},
{"$set": {"parsed_data": parsed_data}}
)
if result.matched_count == 0:
raise HTTPException(status_code=404, detail="Resume not found. Upload a resume first.")
return {"message": "Resume details updated successfully", "parsed_data": parsed_data}
@router.get("/job-descriptions")
async def get_my_job_descriptions(
current_user: dict = Depends(get_current_user),
):
"""List current user's job descriptions."""
items = await list_my_job_descriptions(current_user["user_id"])
return {"items": items}
@router.post("/job-descriptions")
async def create_my_job_description(
request_data: dict,
current_user: dict = Depends(get_current_user),
):
"""Create a new job description for current user."""
try:
item = await create_job_description(
user_id=current_user["user_id"],
owner_role=current_user.get("role", "student"),
title=request_data.get("title"),
company=request_data.get("company"),
description=request_data.get("description"),
required_skills=request_data.get("required_skills"),
)
return item
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
@router.put("/job-descriptions/{jd_id}")
async def update_my_job_description_endpoint(
jd_id: str,
request_data: dict,
current_user: dict = Depends(get_current_user),
):
"""Update a current user's job description."""
try:
item = await update_my_job_description(current_user["user_id"], jd_id, request_data)
return item
except ValueError as e:
status_code = 404 if "not found" in str(e).lower() else 400
raise HTTPException(status_code=status_code, detail=str(e))
@router.delete("/job-descriptions/{jd_id}")
async def delete_my_job_description_endpoint(
jd_id: str,
current_user: dict = Depends(get_current_user),
):
"""Delete a current user's job description."""
success = await delete_my_job_description(current_user["user_id"], jd_id)
if not success:
raise HTTPException(status_code=404, detail="Job description not found")
return {"message": "Job description deleted"}
@router.post("/job-descriptions/parse-file")
async def parse_jd_file_for_user(
file: UploadFile = File(...),
current_user: dict = Depends(get_current_user),
):
"""Upload a JD file (PDF/DOCX/TXT) and extract structured fields via AI."""
if not file.filename:
raise HTTPException(status_code=400, detail="No file provided")
allowed_ext = {".pdf", ".doc", ".docx", ".txt"}
ext = "." + file.filename.rsplit(".", 1)[-1].lower() if "." in file.filename else ""
if ext not in allowed_ext:
raise HTTPException(status_code=400, detail="Unsupported file type. Allowed: PDF, DOC, DOCX, TXT")
content = await file.read()
if len(content) > 10 * 1024 * 1024:
raise HTTPException(status_code=400, detail="File too large. Maximum 10MB")
try:
result = await parse_jd_from_file(file.filename, content)
return result
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to parse JD file: {str(e)}")
# ─── Group Tests (student) ───────────────────────────────────────────────────
@router.get("/group-tests")
async def list_available_group_tests(
current_user: dict = Depends(get_current_user),
):
"""List published group tests available to students."""
items = await list_group_tests(only_published=True)
return {"items": items}
@router.get("/group-tests/my-results")
async def my_group_test_results(
current_user: dict = Depends(get_current_user),
):
"""List all group test results for the current student."""
items = await get_my_group_test_results(current_user["user_id"])
return {"items": items}
@router.post("/group-tests/{group_test_id}/start")
async def start_group_test(
group_test_id: str,
current_user: dict = Depends(get_current_user),
):
"""Start a new attempt for a group test."""
try:
result = await start_group_test_attempt(group_test_id, current_user["user_id"])
return result
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
@router.get("/group-tests/{group_test_id}/my-attempt")
async def get_my_attempt(
group_test_id: str,
current_user: dict = Depends(get_current_user),
):
"""Get student's latest attempt at a group test."""
result = await get_my_group_test_attempt(group_test_id, current_user["user_id"])
return result # may be None
@router.get("/group-tests/results/{result_id}")
async def get_result_detail(
result_id: str,
current_user: dict = Depends(get_current_user),
):
"""Get full detail of a group test result."""
try:
return await get_group_test_result(result_id, current_user["user_id"])
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e))
@router.post("/group-tests/results/{result_id}/link-topic")
async def link_topic_to_result(
result_id: str,
request_data: dict,
current_user: dict = Depends(get_current_user),
):
"""Link a completed interview session to a topic inside a group test result."""
topic_id = (request_data.get("topic_id") or "").strip()
session_id = (request_data.get("session_id") or "").strip()
if not topic_id or not session_id:
raise HTTPException(status_code=400, detail="topic_id and session_id are required")
try:
return await link_topic_session(result_id, current_user["user_id"], topic_id, session_id)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))