Spaces:
Sleeping
Sleeping
File size: 9,987 Bytes
03faf26 1cff1e5 03faf26 1cff1e5 d50ee26 15145f6 d50ee26 15145f6 1cff1e5 5094515 15145f6 1cff1e5 03faf26 1cff1e5 03faf26 1cff1e5 5094515 1cff1e5 03faf26 1cff1e5 03faf26 d50ee26 15145f6 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 | 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))
|