from fastapi import FastAPI, File, UploadFile, Form, HTTPException, Request, Depends from fastapi.responses import JSONResponse from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials from pydantic import BaseModel, HttpUrl from typing import Optional import uvicorn from fastapi.middleware.cors import CORSMiddleware from utils import read_file, fetch_job_description, optimize_resume_api import logging from fastapi_cache import FastAPICache from fastapi_cache.backends.inmemory import InMemoryBackend from fastapi_cache.decorator import cache import hashlib # Set up logging logging.basicConfig(level=logging.DEBUG) logger = logging.getLogger(__name__) app = FastAPI(title="Resume Optimizer API") app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"]) security = HTTPBearer() class OptimizationResponse(BaseModel): optimizedResume: str metadata: dict class ErrorResponse(BaseModel): detail: str def verify_token(credentials: HTTPAuthorizationCredentials = Depends(security)): token = credentials.credentials if token != "your_secret_token": raise HTTPException(status_code=401, detail="Invalid authentication token") return token def generate_cache_key(resume_content: str, job_description_content: str) -> str: combined = resume_content + job_description_content return hashlib.md5(combined.encode()).hexdigest() @app.post("/api/optimize-resume", response_model=OptimizationResponse, responses={400: {"model": ErrorResponse}, 401: {"model": ErrorResponse}, 500: {"model": ErrorResponse}}) @cache(expire=36000000) # Cache for n seconds async def optimize_resume( resume: Optional[UploadFile] = File(None), resumeText: Optional[str] = Form(None), jobDescription: Optional[str] = Form(None), jobDescriptionUrl: Optional[HttpUrl] = Form(None), token: str = Depends(verify_token) ): try: # Input validation if (resume is None) == (resumeText is None): raise ValueError("Provide either resume file or resume text, but not both") if (jobDescription is None) == (jobDescriptionUrl is None): raise ValueError("Provide either job description text or URL, but not both") # Process resume if resume: resume_content = read_file(resume) else: resume_content = resumeText # Process job description if jobDescription: job_description_content = jobDescription else: job_description_content = fetch_job_description(jobDescriptionUrl) # Generate cache key cache_key = generate_cache_key(resume_content, job_description_content) # Check cache cached_result = await FastAPICache.get(cache_key) if cached_result: return OptimizationResponse(**cached_result) # Perform optimization optimized_resume = optimize_resume_api(resume_content, job_description_content) metadata = { "original_resume": resume_content, "original_jd": job_description_content } result = OptimizationResponse( optimizedResume=optimized_resume, metadata=metadata ) # Cache the result await FastAPICache.set(cache_key, result.dict(), expire=36000000) return result except ValueError as ve: logger.error(f"ValueError occurred: {str(ve)}", exc_info=True) raise HTTPException(status_code=400, detail=str(ve)) except Exception as e: logger.error(f"Unexpected error occurred: {str(e)}", exc_info=True) raise HTTPException(status_code=500, detail=f"An unexpected error occurred: {str(e)}") @app.exception_handler(HTTPException) async def http_exception_handler(request: Request, exc: HTTPException): return JSONResponse(status_code=exc.status_code, content={"detail": exc.detail}) @app.on_event("startup") async def startup(): FastAPICache.init(InMemoryBackend(), prefix="fastapi-cache") if __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=8000, debug=True)