diff --git a/backend/src/api/routes/job_routes.py b/backend/src/api/routes/job_routes.py index 9fd7bce..7c1f557 100644 --- a/backend/src/api/routes/job_routes.py +++ b/backend/src/api/routes/job_routes.py @@ -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) # 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") @@ -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) 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: diff --git a/backend/src/api/routes/resume_routes.py b/backend/src/api/routes/resume_routes.py index cb773d3..6d630e3 100644 --- a/backend/src/api/routes/resume_routes.py +++ b/backend/src/api/routes/resume_routes.py @@ -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, diff --git a/backend/src/api/utils.py b/backend/src/api/utils.py index cf4993d..a5de908 100644 --- a/backend/src/api/utils.py +++ b/backend/src/api/utils.py @@ -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.""" @@ -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. + + 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 + +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) """ - 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' + except Exception as e: + await file.seek(0) + raise HTTPException( + status_code=400, + detail=f"Error occurred while validating DOCX file {file.filename}: {e}" + ) + + 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 \ No newline at end of file + return detected_mime + diff --git a/backend/src/services/document_processing/document_service.py b/backend/src/services/document_processing/document_service.py index 6bc046f..12a78c2 100644 --- a/backend/src/services/document_processing/document_service.py +++ b/backend/src/services/document_processing/document_service.py @@ -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) return tmp_file_path @staticmethod