Skip to content
Open
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
18 changes: 2 additions & 16 deletions backend/src/api/routes/job_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,23 +54,14 @@ async def upload_job_description(
if not file.filename:
raise HTTPException(status_code=400, detail="File must have a filename")

is_valid, _ = await validate_document_file(file)

if not is_valid:
raise HTTPException(
status_code=400,
detail=f"Invalid file type. Accepted formats: PDF, DOC, DOCX"
)
is_valid, tmp_file_path, _, _ = await validate_document_file(file)

Copilot AI Apr 17, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The is_valid value returned by validate_document_file is assigned but never used. Since the helper now raises HTTPException on invalid input, consider unpacking to _ (or removing the boolean from the helper's return type) to avoid unused variables and make the control flow clearer.

Suggested change
is_valid, tmp_file_path, _, _ = await validate_document_file(file)
_, tmp_file_path, _, _ = await validate_document_file(file)

Copilot uses AI. Check for mistakes.

# Validate company name is picked by user
if not company_name:
raise HTTPException(status_code=400, detail="Company name is required")

# try block to process the document with Azure Content Understanding
tmp_file_path = None
try:
tmp_file_path = await save_upload_file_tmp(file)

subscription_key = os.getenv("AZURE_CONTENT_UNDERSTANDING_SUBSCRIPTION_KEY")
if not subscription_key:
raise HTTPException(status_code=500, detail="Azure API key not configured")
Expand Down Expand Up @@ -457,16 +448,11 @@ async def upload_culture_document(

if not file.filename:
raise HTTPException(status_code=400, detail="File must have a filename")
is_valid, _ = await validate_document_file(file)
if not is_valid:
raise HTTPException(status_code=400, detail="Invalid file type. Accepted formats: PDF, DOC, DOCX")
is_valid, tmp_file_path, _, _ = await validate_document_file(file)

Copilot AI Apr 17, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The is_valid value returned by validate_document_file is assigned but never used. Since validate_document_file raises on invalid input, consider unpacking to _ (or removing the boolean from the helper return) to avoid unused variables.

Suggested change
is_valid, tmp_file_path, _, _ = await validate_document_file(file)
_, tmp_file_path, _, _ = await validate_document_file(file)

Copilot uses AI. Check for mistakes.

get_job_or_404(job_id)

tmp_file_path = None
try:
tmp_file_path = await save_upload_file_tmp(file)

try:
culture_text = await run_in_threadpool(extract_text_from_culture_doc, tmp_file_path)
except RuntimeError as e:
Expand Down
2 changes: 1 addition & 1 deletion backend/src/api/routes/resume_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ async def upload_resume(
# Validate file type
if not file.filename:
raise HTTPException(status_code=400, detail="File must have a filename")
is_valid, _ = await validate_document_file(file)
is_valid, tmp_file_path, _, _= await validate_document_file(file)
if not is_valid:
raise HTTPException(
status_code=400,
Expand Down
84 changes: 69 additions & 15 deletions backend/src/api/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@
import os
import tempfile
import logging
from typing import Tuple
from fastapi import HTTPException, UploadFile
from bson import ObjectId
from bson.errors import InvalidId

logger = logging.getLogger(__name__)

ALLOWED_MIME_TYPES= {
'application/pdf',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
}

def validate_object_id(id_str: str) -> None:
"""Validate that a string is a valid MongoDB ObjectId format."""
Expand Down Expand Up @@ -55,36 +59,86 @@ def cleanup_temp_file(file_path: str) -> None:
except Exception as e:
logger.warning(f"Failed to cleanup temp file {file_path}: {e}")

# function for validating document file types
async def validate_document_file(file: UploadFile) -> Tuple[bool, str]:
# function for validating document file size and type
async def validate_document_file(file: UploadFile) -> tuple[bool, str, int, str]:
"""
Orchestrates the full document ingestion pipeline: validates the uploaded file,
saves it to a temporary location, and converts it to PDF if necessary.
Comment on lines +65 to +66

Copilot AI Apr 17, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The validate_document_file docstring says it "converts it to PDF if necessary", but the function only validates and saves the file. Please update the docstring to match the actual behavior to avoid misleading future changes/users.

Suggested change
Orchestrates the full document ingestion pipeline: validates the uploaded file,
saves it to a temporary location, and converts it to PDF if necessary.
Validates the uploaded file, saves it to a temporary location,
and returns the validation result and file metadata.

Copilot uses AI. Check for mistakes.

Args:
file: FastAPI UploadFile object

Returns:
Tuple of (is_valid, temp_path, file_size, file_type)

Raises:
HTTPException: If the file is missing, exceeds size limits, or is of an unsupported type.
"""
if not file:
raise HTTPException(
status_code=400,
detail="No file provided. Please upload a PDF, DOC, or DOCX file."
)
file_size = await validate_file_size(file)
file_type = await validate_file_type(file)

temp_file_path = await save_upload_file_tmp(file)

return True, temp_file_path, file_size, file_type
Comment on lines +82 to +87

Copilot AI Apr 17, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

validate_document_file reads and saves the upload to a temp file via save_upload_file_tmp(file), which consumes the UploadFile stream. Callers that subsequently do await file.read() will get empty bytes unless the stream is rewound. Either rewind the upload (await file.seek(0)) after saving, or change this helper to avoid consuming the stream / return the bytes, so downstream endpoints can still read the file content reliably.

Copilot uses AI. Check for mistakes.

async def validate_file_size(file: UploadFile, max_size_kb: int = 500) -> int:
"""
Validate that the file's type is acceptable based on its content, not extension.
Acceptable file types are PDF, DOC, and DOCX.
Validate that the uploaded file does not exceed the maximum allowed size.

Args:
file: FastAPI UploadFile object
max_size_kb: Maximum allowed file size in kilobytes (default is 500 KB)

Returns:
Tuple of (is_valid, file_extension)
Tuple of (is_valid, file_size_bytes)

Copilot AI Apr 17, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

validate_file_size is annotated to return int, but the docstring says it returns a tuple "(is_valid, file_size_bytes)". Please correct the Returns section so it matches the actual return type/value.

Suggested change
Tuple of (is_valid, file_size_bytes)
The file size in bytes.

Copilot uses AI. Check for mistakes.
"""
if not file:
max_size_bytes = max_size_kb * 1024
file_size_bytes = file.size
if file_size_bytes is None:
current_pos = file.file.tell()
file.file.seek(0, 2)
file_size_bytes = file.file.tell()
file.file.seek(current_pos)
if file_size_bytes > max_size_bytes:
raise HTTPException(
status_code=400,
detail="No file provided. Please upload a PDF, DOC, or DOCX file."
detail=f"File size exceeds the maximum allowed limit of {max_size_kb} KB."
)
return file_size_bytes

async def validate_file_type(file: UploadFile) -> str:
import filetype
allowed_mime_types = (
'application/pdf',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
)
from docx import Document

content = await file.read(261)
file_type = filetype.guess(content)
await file.seek(0)
if not file_type or file_type.mime not in allowed_mime_types:

detected_mime = file_type.mime if file_type else None
logger.info(f"Detected MIME type for {file.filename}: {detected_mime if detected_mime else 'Unknown'}")

if detected_mime == 'application/zip':
try:
Document(file.file)
await file.seek(0)
detected_mime = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
Comment on lines +125 to +129

Copilot AI Apr 17, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

validate_file_type calls python-docx's Document(...) directly inside an async request handler. This is synchronous and can block the event loop for large/complex files. Consider running DOCX parsing in a threadpool (or using a lightweight ZIP/content-type check) so uploads don't stall other requests.

Copilot uses AI. Check for mistakes.
except Exception as e:
await file.seek(0)
raise HTTPException(
status_code=400,
detail=f"Error occurred while validating DOCX file {file.filename}: {e}"
Comment on lines +132 to +134

Copilot AI Apr 17, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The HTTP 400 error detail for DOCX validation includes the raw exception ({e}), which may leak internal parsing details to clients. Prefer logging the exception server-side and returning a generic client-facing message (e.g., "Invalid DOCX file") with a stable error code/context.

Suggested change
raise HTTPException(
status_code=400,
detail=f"Error occurred while validating DOCX file {file.filename}: {e}"
logger.warning(f"Invalid DOCX file during validation: {file.filename}: {e}")
raise HTTPException(
status_code=400,
detail="Invalid DOCX file."

Copilot uses AI. Check for mistakes.
)

if not detected_mime or detected_mime not in ALLOWED_MIME_TYPES:
raise HTTPException(
status_code=400,
detail=f"Unsupported file type: {file.filename}. Only PDF, DOC, and DOCX files are supported."
)

return True, file_type.mime
return detected_mime

11 changes: 1 addition & 10 deletions backend/src/services/document_processing/document_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,16 +145,7 @@ async def validate_and_save_temp_file(file: UploadFile) -> str:
Returns: temporary file path
"""
# Validate file type
is_valid, _ = await validate_document_file(file)

if not is_valid:
raise HTTPException(
status_code=400,
detail=f"Invalid file type. Accepted formats: PDF, DOC, DOCX"
)

# Save to temporary location
tmp_file_path = await save_upload_file_tmp(file)
is_valid, tmp_file_path, _, _ = await validate_document_file(file)

Copilot AI Apr 17, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

validate_document_file now raises on invalid input, so the is_valid return value is unused here. Consider unpacking to _ (or updating validate_document_file to not return a boolean) to reduce confusion and avoid unused variables.

Suggested change
is_valid, tmp_file_path, _, _ = await validate_document_file(file)
_, tmp_file_path, _, _ = await validate_document_file(file)

Copilot uses AI. Check for mistakes.
return tmp_file_path

@staticmethod
Expand Down