|
|
""" |
|
|
CV Upload Router. |
|
|
|
|
|
Handles CV submission and candidate registration. |
|
|
""" |
|
|
|
|
|
from pathlib import Path |
|
|
from fastapi import APIRouter, HTTPException, UploadFile, File, Form |
|
|
|
|
|
from src.backend.api.schemas.cv_upload import SubmitResponse |
|
|
from src.backend.configs import get_cv_settings |
|
|
from src.backend.database.candidates import register_candidate, update_parsed_cv_path |
|
|
from src.backend.database.cvs import save_cv |
|
|
from src.backend.doc_parser import pdf_to_markdown |
|
|
|
|
|
|
|
|
router = APIRouter() |
|
|
|
|
|
|
|
|
settings = get_cv_settings() |
|
|
settings.ensure_dirs() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/submit", response_model=SubmitResponse) |
|
|
async def submit_application( |
|
|
full_name: str = Form(..., description="Candidate's full name"), |
|
|
email: str = Form(..., description="Candidate's email address"), |
|
|
phone: str = Form(default="", description="Candidate's phone number"), |
|
|
cv_file: UploadFile = File(..., description="CV file (PDF or DOCX)") |
|
|
) -> SubmitResponse: |
|
|
""" |
|
|
Submit a job application with CV. |
|
|
|
|
|
This endpoint: |
|
|
1. Saves the uploaded CV file |
|
|
2. Registers the candidate in the database |
|
|
3. Parses the CV to markdown for AI processing |
|
|
4. Updates the parsed CV path in the database |
|
|
|
|
|
Returns success status and details about the submission. |
|
|
""" |
|
|
|
|
|
allowed_extensions = {".pdf", ".docx"} |
|
|
file_ext = Path(cv_file.filename or "").suffix.lower() |
|
|
if file_ext not in allowed_extensions: |
|
|
raise HTTPException( |
|
|
status_code=400, |
|
|
detail=f"Invalid file type. Allowed: {', '.join(allowed_extensions)}" |
|
|
) |
|
|
|
|
|
try: |
|
|
|
|
|
file_path = save_cv(cv_file.file, cv_file.filename or "cv.pdf", candidate_name=full_name) |
|
|
file_path = Path(file_path) |
|
|
|
|
|
|
|
|
success = register_candidate(full_name, email, phone, str(file_path)) |
|
|
|
|
|
if not success: |
|
|
return SubmitResponse( |
|
|
success=False, |
|
|
message=f"An application with email '{email}' already exists. You can only apply once.", |
|
|
candidate_name=full_name, |
|
|
email=email, |
|
|
already_exists=True, |
|
|
) |
|
|
|
|
|
|
|
|
pdf_to_markdown( |
|
|
input_path=file_path, |
|
|
output_path=settings.parsed_path, |
|
|
model="gpt-4.1-mini", |
|
|
) |
|
|
|
|
|
|
|
|
parsed_path = settings.parsed_path / (file_path.stem + ".txt") |
|
|
update_parsed_cv_path(email, str(parsed_path)) |
|
|
|
|
|
return SubmitResponse( |
|
|
success=True, |
|
|
message=f"Application submitted successfully for {full_name}!", |
|
|
candidate_name=full_name, |
|
|
email=email, |
|
|
cv_file_path=str(file_path), |
|
|
) |
|
|
|
|
|
except Exception as e: |
|
|
raise HTTPException( |
|
|
status_code=500, |
|
|
detail=f"Failed to process application: {str(e)}" |
|
|
) |
|
|
|
|
|
|
|
|
@router.get("/health") |
|
|
async def cv_upload_health(): |
|
|
"""Health check for CV upload router.""" |
|
|
return {"status": "healthy", "service": "cv_upload"} |
|
|
|
|
|
|